From 8d3fde2d0fcf34f421f516f54c9b2916bca687db Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sat, 10 May 2025 07:53:33 +0200 Subject: [PATCH 01/56] no more badges vscode marketplace does not allow svg and github actions do not offer png :-( --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 8cc650ae..e0b7959c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -[![Python CI](https://github.com/skogsbaer/write-your-python-program/actions/workflows/github-action-test-python.yml/badge.svg)](https://github.com/skogsbaer/write-your-python-program/actions/workflows/github-action-test-python.yml) -[![Node.js CI](https://github.com/skogsbaer/write-your-python-program/actions/workflows/github-action-test-js.yml/badge.svg)](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. From bf262a62dabd1a15ef5c11396d48fe9c4d150fd6 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sun, 11 May 2025 22:15:37 +0200 Subject: [PATCH 02/56] version bump and changelog for 1.3.1 and 1.3.2 --- ChangeLog.md | 4 ++++ package.json | 2 +- python/setup.py | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c2ecf21c..97d96afe 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ # Write Your Python Program - CHANGELOG +* 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/package.json b/package.json index 39112370..d0b9d403 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Write Your Python Program!", "description": "A user friendly python environment for beginners", "license": "See license in LICENSE", - "version": "1.3.0", + "version": "1.3.2", "publisher": "StefanWehr", "icon": "icon.png", "engines": { diff --git a/python/setup.py b/python/setup.py index bc0e9e21..909ca170 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 @@ -33,7 +32,7 @@ def readVersion(): 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.*']), + packages=['wypp'] + find_packages("deps/untypy", exclude=['test', 'test.*']), python_requires='>=3.12.0', scripts=['wypp'] ) From 7c42534690a642a6b98cb42ea067bdf751e423d1 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 26 May 2025 10:43:19 +0200 Subject: [PATCH 03/56] more tests --- python/deps/untypy/untypy/util/wrapper.py | 27 +++++-- python/test-data/testDisappearingObject_01.py | 6 +- .../test-data/testDisappearingObject_03.err | 0 .../test-data/testDisappearingObject_03.out | 2 + python/test-data/testDisappearingObject_03.py | 76 +++++++++++++++++++ python/test-data/testTypesCollections1.py | 2 +- 6 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 python/test-data/testDisappearingObject_03.err create mode 100644 python/test-data/testDisappearingObject_03.out create mode 100644 python/test-data/testDisappearingObject_03.py diff --git a/python/deps/untypy/untypy/util/wrapper.py b/python/deps/untypy/untypy/util/wrapper.py index 1ffe3eae..f1a5494f 100644 --- a/python/deps/untypy/untypy/util/wrapper.py +++ b/python/deps/untypy/untypy/util/wrapper.py @@ -1,5 +1,6 @@ import typing import collections +import abc from untypy.util.debug import debug def _f(): @@ -207,18 +208,34 @@ def wrapSimple(wrapped, methods, name, extra, cls=SimpleWrapper): methods[x] = attr return _wrap(wrapped, methods, mod, name, extra, cls) +_wrapperClasses = {} + def wrapObj(wrapped, methods, name, extra): - class BaseWrapper(WrapperBase, wrapped.__class__): - def __init__(self, wrapped): - self.__dict__ = wrapped.__dict__ - self.__wrapped__ = wrapped + k = wrapped.__class__ + if k in _wrapperClasses: + cls = _wrapperClasses[k] + else: + if isinstance(wrapped, abc.ABC): + class BaseWrapper(WrapperBase, wrapped.__class__, abc.ABC): + def __init__(self, wrapped): + self.__dict__ = wrapped.__dict__ + self.__wrapped__ = wrapped + _wrapperClasses[k] = BaseWrapper + cls = BaseWrapper + else: + class BaseWrapper(WrapperBase, wrapped.__class__): + def __init__(self, wrapped): + self.__dict__ = wrapped.__dict__ + self.__wrapped__ = wrapped + _wrapperClasses[k] = BaseWrapper + cls = BaseWrapper 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) + return _wrap(wrapped, methods, mod, name, extra, cls) def wrapBuiltin(wrapped, methods, name, extra, cls): if name is None: diff --git a/python/test-data/testDisappearingObject_01.py b/python/test-data/testDisappearingObject_01.py index aabc05fa..68d291c2 100644 --- a/python/test-data/testDisappearingObject_01.py +++ b/python/test-data/testDisappearingObject_01.py @@ -1,13 +1,11 @@ # 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 + self._name = name def getName(self) -> str: - return self.name + return self._name def getContent(self) -> str: raise Exception('No content') def getChildren(self) -> list[FileSystemEntry]: diff --git a/python/test-data/testDisappearingObject_03.err b/python/test-data/testDisappearingObject_03.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testDisappearingObject_03.out b/python/test-data/testDisappearingObject_03.out new file mode 100644 index 00000000..3252461b --- /dev/null +++ b/python/test-data/testDisappearingObject_03.out @@ -0,0 +1,2 @@ +['subdir'] +Directory('stefan') diff --git a/python/test-data/testDisappearingObject_03.py b/python/test-data/testDisappearingObject_03.py new file mode 100644 index 00000000..2a2057fd --- /dev/null +++ b/python/test-data/testDisappearingObject_03.py @@ -0,0 +1,76 @@ +# This test comes from a bug reported by a student, 2025-05-8 +from __future__ import annotations +import abc + +class FileSystemEntry(abc.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: + 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/testTypesCollections1.py b/python/test-data/testTypesCollections1.py index be569e10..05fb7758 100644 --- a/python/test-data/testTypesCollections1.py +++ b/python/test-data/testTypesCollections1.py @@ -1,6 +1,6 @@ from wypp import * -def appendSomething(l: list[int]) -> None: +def appendSomething(l: list[int]): l.append("foo") l = [1,2,3] From 8cb1064de901cea49094e2db109b6c01e8b4b55b Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 26 May 2025 10:43:50 +0200 Subject: [PATCH 04/56] port fileTests to python --- python/allTests | 2 +- python/allTestsForPyVersion | 2 +- python/fileTests | 310 ----------------------------- python/fileTests.py | 384 ++++++++++++++++++++++++++++++++++++ 4 files changed, 386 insertions(+), 312 deletions(-) delete mode 100755 python/fileTests create mode 100644 python/fileTests.py diff --git a/python/allTests b/python/allTests index 3837a1e3..70a3ed75 100755 --- a/python/allTests +++ b/python/allTests @@ -16,4 +16,4 @@ function run() } PYENV_VERSION=3.12.1 run -PYENV_VERSION=3.13.0 run +PYENV_VERSION=3.13.2 run diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion index fac2ba23..408d2b38 100755 --- a/python/allTestsForPyVersion +++ b/python/allTestsForPyVersion @@ -65,4 +65,4 @@ else fi echo "Running file tests ..." -./fileTests +python3 ./fileTests.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..8781b620 --- /dev/null +++ b/python/fileTests.py @@ -0,0 +1,384 @@ +from dataclasses import dataclass +import os +from typing import * +import sys +import subprocess +import tempfile +import argparse +import re + +@dataclass(frozen=True) +class TestOpts: + cmd: str + baseDir: str + startAt: Optional[str] + only: Optional[str] + keepGoing: bool + +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") + + # Parse the arguments + args = parser.parse_args() + + scriptDir = os.path.dirname(os.path.abspath(__file__)) + return TestOpts( + cmd=f'{scriptDir}/src/runYourProgram.py', + baseDir=scriptDir, + startAt=args.start_at, + only=args.only, + keepGoing=args.keepGoing + ) + +TestStatus = Literal['passed', 'failed', 'skipped'] + +@dataclass +class TestResults: + passed: list[str] + failed: list[str] + skipped: list[str] + def record(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) -> str: + 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]) + return False + else: + return True + +def checkInstall(testFile: str, ctx: TestContext=globalCtx): + if shouldSkip(testFile, ctx, None): + ctx.results.record(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 "/[^"]*"', ' File ""', content, flags=re.MULTILINE) # Remove file paths + with open(filePath, 'w') as f: + f.write(content) + +def _check(testFile: str, + exitCode: int, + typecheck: bool, + args: list[str], + pythonPath: list[str], + minVersion: Optional[tuple[int, int]], + ctx: TestContext, + what: str) -> TestStatus: + if shouldSkip(testFile, ctx, minVersion): + return 'skipped' + + # Prepare expected output files + baseFile = os.path.splitext(testFile)[0] + expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) + expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) + + # Prepare the command + cmd = [sys.executable, ctx.opts.cmd, '--quiet'] + if not typecheck: + cmd.append('--no-typechecking') + cmd.append(testFile) + cmd.extend(args) + + with tempfile.TemporaryDirectory() as d: + actualStdoutFile = os.path.join(d, 'stdout.txt') + actualStderrFile = os.path.join(d, 'stderr.txt') + env = os.environ.copy() + env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) + 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' + + fixOutput(actualStdoutFile) + fixOutput(actualStderrFile) + + # Checkout outputs + if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): + return 'failed' + if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): + return 'failed' + + # If all checks pass + print(f"{testFile}{what} OK") + return 'passed' + +def check(testFile: str, + exitCode: int = 1, + typecheck: bool = True, + args: list[str] = [], + pythonPath: list[str] = [], + minVersion: Optional[tuple[int, int]] = None, + ctx: TestContext = globalCtx, + what: str = ''): + status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, ctx, what) + ctx.results.record(testFile, status) + if status == 'failed': + if not ctx.opts.keepGoing: + ctx.results.finish() + +def checkBoth(testFile: str, + exitCode: int = 1, + args: list[str] = [], + minVersion: Optional[tuple[int, int]] = None, + ctx: TestContext = globalCtx): + check(testFile, exitCode, typecheck=True, args=args, minVersion=minVersion, ctx=ctx, what=' (typecheck)') + check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') + +checkInstall('test-data/fileWithImport.py') +checkInstall('test-data/fileWithoutImport.py') +checkInstall('test-data/fileWithBothImports.py') +checkInstall('test-data/fileWithRecursiveTypes.py') +checkBoth('test-data/testTraceback.py') +checkBoth('test-data/testTraceback2.py') +checkBoth('test-data/testTraceback3.py') +checkBoth('test-data/testArgs.py', exitCode=0, args=['ARG_1', 'ARG_2']) +checkBoth('test-data/printModuleName.py', exitCode=0) +checkBoth('test-data/printModuleNameImport.py', exitCode=0) +checkBoth('test-data/testTypes1.py') +checkBoth('test-data/testIndexError.py') +checkBoth('test-data/testWrapperError.py') +checkBoth('test-data/testCheckFail.py', exitCode=0) +check('test-data/testTypes2.py', exitCode=1) +check('test-data/testTypes2.py', exitCode=0, typecheck=False) +check('test-data/testABCMeta.py') +check('test-data/testClassHierarchy.py', exitCode=0) +check('test-data/testTypesCollections1.py') +check('test-data/testTypesCollections2.py') +check('test-data/testTypesCollections3.py') +check('test-data/testTypesCollections4.py') +check('test-data/testTypesProtos1.py') +check('test-data/testTypesProtos2.py') +check('test-data/testTypesProtos3.py') +check('test-data/testTypesProtos4.py') +check('test-data/testTypesSubclassing1.py') +check('test-data/testTypesHigherOrderFuns.py') +check('test-data/testTypesHigherOrderFuns2.py', exitCode=0) +check('test-data/testTypesHigherOrderFuns3.py') +check('test-data/testTypesHigherOrderFuns4.py', exitCode=0) +check('test-data/testTypesHigherOrderFuns5.py', exitCode=0) +check('test-data/testTypesRecordInheritance.py') +check('test-data/testRecordSetTypes.py') +check('test-data/testRecordSetTypeForwardRef.py') +check('test-data/testForwardRef.py', exitCode=0) +check('test-data/testForwardRef1.py', exitCode=0) +check('test-data/testForwardRef2.py') +check('test-data/testForwardRef3.py', exitCode=0) +check('test-data/testForwardRef4.py') +check('test-data/testForwardRef5.py') +check('test-data/testForwardRef6.py', exitCode=0) +check('test-data/testHintParentheses1.py') +check('test-data/testHintParentheses2.py') +check('test-data/testHintParentheses3.py') +check('test-data/testTypesReturn.py') +check('test-data/testMissingReturn.py') +check('test-data/testTypesSequence1.py') +check('test-data/testTypesSequence2.py') +check('test-data/testTypesTuple1.py') +check('test-data/wrong-caused-by.py') +check('test-data/declared-at-missing.py') +check('test-data/testTypesSet1.py') +check('test-data/testTypesSet2.py') +check('test-data/testTypesSet3.py') +check('test-data/testTypesSet4.py') +check('test-data/testTypesDict1.py') +check('test-data/testTypesDict2.py') +check('test-data/testTypesDict3.py') +check('test-data/testTypesDict4.py') +check('test-data/testIterableImplicitAny.py') +check('test-data/testDoubleWrappingDicts.py', exitCode=0) +check('test-data/testClassRecursion.py', exitCode=0) +check('test-data/testWrongNumOfArguments.py') +check('test-data/testWrongNumOfArguments2.py') +check('test-data/testLiteralInstanceOf.py', exitCode=0) +check('test-data/testWrongKeywordArg.py') +check('test-data/testWrongKeywordArg2.py') +check('test-data/testComplex.py', exitCode=0) +check('test-data/testUnionLiteral.py', exitCode=0) +check('test-data/testCheck.py', exitCode=0) +check('test-data/testNameErrorBug.py', exitCode=0) +check('test-data/testOriginalTypeNames.py', exitCode=0) +check('test-data/testDeepEqBug.py', exitCode=0) +check('test-data/testLockFactory.py') +check('test-data/testLockFactory2.py') +check('test-data/testGetSource.py') +check('test-data/testDict.py', exitCode=0) +check('test-data/testFunEq.py', exitCode=0) +check('test-data/testBugSliceIndices.py', exitCode=0) +check('test-data/testABC.py', exitCode=0) +check('test-data/testTypesWrapperEq.py', exitCode=0) +check('test-data/testTypesProtos5.py', exitCode=0) +check('test-data/testTypesProtos6.py') +check('test-data/testTypesProtos7.py') +check('test-data/testTypesProtos8.py') +check('test-data/testTypesProtos9.py') +check('test-data/testIterable1.py', exitCode=0) +check('test-data/testIterable2.py', exitCode=0) +check('test-data/testIterable3.py', exitCode=0) +check('test-data/testIterable4.py', exitCode=0) +check('test-data/testIterable5.py', exitCode=0) +check('test-data/testIterable6.py', exitCode=0) +check('test-data/testIterable7.py') +check('test-data/testIterator.py', exitCode=0) +check('test-data/testIterator2.py', exitCode=0) +check('test-data/testIterator3.py', exitCode=0) +check('test-data/testIterator4.py', exitCode=0) +check('test-data/testConcat.py', exitCode=0) +check('test-data/testCopy.py', exitCode=0) +check('test-data/testHof.py', exitCode=0) +check('test-data/testIndexSeq.py', exitCode=0) +check('test-data/testWrap.py', exitCode=0) +check('test-data/testWrap2.py', exitCode=0) +check('test-data/testTodo.py') +check('test-data/testImpossible.py') +check('test-data/testInvalidLiteral.py') +check('test-data/admin.py', exitCode=0) +check('test-data/modules/A/main.py', args=['--extra-dir', 'test-data/modules/B'], + pythonPath=['test-data/modules/B']) +check('test-data/testUnion.py', exitCode=0) +check('test-data/testUnion2.py', exitCode=0) +check('test-data/testUnion3.py', exitCode=0) +check('test-data/testLiteral1.py') +check('test-data/testForwardTypeInRecord.py', exitCode=0) +check('test-data/testForwardTypeInRecord2.py', exitCode=0) +check('test-data/testUnionOfUnion.py', exitCode=0) +check('test-data/testRecordTypes.py') +#check('test-data/testDisappearingObject_01.py', exitCode=0) +#check('test-data/testDisappearingObject_02.py', exitCode=0) +#check('test-data/testDisappearingObject_03.py', exitCode=0) +checkBoth('test-data/testTypeKeyword.py', exitCode=0, minVersion=(3,12)) + +globalCtx.results.finish() From 0e4e04647b8f272b5e5bfe76561bb98b8c66b769 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 27 May 2025 09:25:05 +0200 Subject: [PATCH 05/56] remove untypy --- python/deps/untypy/.gitignore | 1 - python/deps/untypy/LICENSE | 21 - python/deps/untypy/README.rst | 43 -- python/deps/untypy/examples/ex01.py | 18 - python/deps/untypy/examples/ex02.py | 26 - python/deps/untypy/examples/ex03.py | 14 - python/deps/untypy/examples/ex04.py | 17 - python/deps/untypy/examples/ex05.py | 30 - python/deps/untypy/examples/ex06.py | 15 - python/deps/untypy/examples/ex09.py | 11 - python/deps/untypy/examples/ex10.py | 17 - python/deps/untypy/examples/mylogfile | 0 python/deps/untypy/requirements.txt | 0 python/deps/untypy/runtests.sh | 4 - python/deps/untypy/setup.py | 12 - python/deps/untypy/test-requirements.txt | 0 python/deps/untypy/test/__init__.py | 3 - python/deps/untypy/test/impl/__init__.py | 0 python/deps/untypy/test/impl/test_any.py | 15 - python/deps/untypy/test/impl/test_callable.py | 78 --- python/deps/untypy/test/impl/test_dict.py | 87 --- .../deps/untypy/test/impl/test_forwardRef.py | 57 -- .../deps/untypy/test/impl/test_generator.py | 167 ------ .../untypy/test/impl/test_interface_dict.py | 235 -------- .../untypy/test/impl/test_interface_set.py | 90 --- python/deps/untypy/test/impl/test_list.py | 348 ------------ python/deps/untypy/test/impl/test_literal.py | 29 - python/deps/untypy/test/impl/test_none.py | 35 -- python/deps/untypy/test/impl/test_optional.py | 49 -- python/deps/untypy/test/impl/test_protocol.py | 280 ---------- python/deps/untypy/test/impl/test_set.py | 171 ------ python/deps/untypy/test/impl/test_simple.py | 160 ------ python/deps/untypy/test/impl/test_str.py | 96 ---- python/deps/untypy/test/impl/test_tuple.py | 156 ------ python/deps/untypy/test/impl/test_union.py | 71 --- .../untypy/test/patching_dummy/__init__.py | 0 .../untypy/test/patching_dummy/a/__init__.py | 5 - .../untypy/test/patching_dummy/a/a_sub.py | 2 - .../test/patching_dummy/argument_types.py | 9 - .../untypy/test/patching_dummy/async_gen.py | 16 - .../untypy/test/patching_dummy/b/__init__.py | 5 - .../untypy/test/patching_dummy/b/b_sub.py | 2 - python/deps/untypy/test/patching_dummy/c.py | 5 - .../test/patching_dummy/patching_classes.py | 6 - .../patching_does_not_change_signature.py | 7 - .../untypy/test/patching_dummy/unpatched.py | 2 - python/deps/untypy/test/test_patching.py | 24 - .../untypy/test/test_standalone_checker.py | 21 - python/deps/untypy/test/util.py | 35 -- python/deps/untypy/test/util_test/__init__.py | 0 .../test/util_test/test_return_traces.py | 36 -- .../untypy/test/util_test/test_wrapper.py | 170 ------ .../untypy/test/util_test/untypy_test_case.py | 30 - python/deps/untypy/untypy/__init__.py | 187 ------- python/deps/untypy/untypy/error.py | 384 ------------- python/deps/untypy/untypy/impl/__init__.py | 108 ---- python/deps/untypy/untypy/impl/alias.py | 12 - python/deps/untypy/untypy/impl/annotated.py | 137 ----- python/deps/untypy/untypy/impl/any.py | 34 -- python/deps/untypy/untypy/impl/callable.py | 277 --------- python/deps/untypy/untypy/impl/choice.py | 66 --- .../deps/untypy/untypy/impl/dummy_delayed.py | 43 -- python/deps/untypy/untypy/impl/generator.py | 128 ----- python/deps/untypy/untypy/impl/generic.py | 98 ---- python/deps/untypy/untypy/impl/interface.py | 87 --- .../untypy/untypy/impl/interfaces/__init__.py | 0 .../untypy/untypy/impl/interfaces/dict.py | 68 --- .../untypy/untypy/impl/interfaces/iterable.py | 25 - .../untypy/untypy/impl/interfaces/list.py | 56 -- .../untypy/untypy/impl/interfaces/sequence.py | 31 -- .../deps/untypy/untypy/impl/interfaces/set.py | 53 -- .../untypy/untypy/impl/interfaces/util.py | 6 - python/deps/untypy/untypy/impl/literal.py | 36 -- python/deps/untypy/untypy/impl/none.py | 27 - python/deps/untypy/untypy/impl/optional.py | 44 -- python/deps/untypy/untypy/impl/protocol.py | 524 ------------------ python/deps/untypy/untypy/impl/simple.py | 68 --- .../untypy/untypy/impl/string_forward_refs.py | 19 - python/deps/untypy/untypy/impl/tuple.py | 103 ---- python/deps/untypy/untypy/impl/union.py | 80 --- python/deps/untypy/untypy/interfaces.py | 105 ---- .../deps/untypy/untypy/patching/__init__.py | 59 -- .../untypy/untypy/patching/ast_transformer.py | 165 ------ .../untypy/untypy/patching/import_hook.py | 65 --- .../untypy/patching/standalone_checker.py | 59 -- python/deps/untypy/untypy/util/__init__.py | 260 --------- python/deps/untypy/untypy/util/condition.py | 130 ----- python/deps/untypy/untypy/util/debug.py | 24 - python/deps/untypy/untypy/util/display.py | 44 -- .../deps/untypy/untypy/util/return_traces.py | 75 --- .../deps/untypy/untypy/util/source_utils.py | 36 -- .../untypy/util/tranformer_combinator.py | 11 - .../deps/untypy/untypy/util/typedfunction.py | 218 -------- python/deps/untypy/untypy/util/typehints.py | 133 ----- python/deps/untypy/untypy/util/wrapper.py | 292 ---------- python/site-lib/untypy | 1 - 96 files changed, 7109 deletions(-) delete mode 100644 python/deps/untypy/.gitignore delete mode 100644 python/deps/untypy/LICENSE delete mode 100644 python/deps/untypy/README.rst delete mode 100644 python/deps/untypy/examples/ex01.py delete mode 100644 python/deps/untypy/examples/ex02.py delete mode 100644 python/deps/untypy/examples/ex03.py delete mode 100644 python/deps/untypy/examples/ex04.py delete mode 100644 python/deps/untypy/examples/ex05.py delete mode 100644 python/deps/untypy/examples/ex06.py delete mode 100644 python/deps/untypy/examples/ex09.py delete mode 100644 python/deps/untypy/examples/ex10.py delete mode 100644 python/deps/untypy/examples/mylogfile delete mode 100644 python/deps/untypy/requirements.txt delete mode 100755 python/deps/untypy/runtests.sh delete mode 100644 python/deps/untypy/setup.py delete mode 100644 python/deps/untypy/test-requirements.txt delete mode 100644 python/deps/untypy/test/__init__.py delete mode 100644 python/deps/untypy/test/impl/__init__.py delete mode 100644 python/deps/untypy/test/impl/test_any.py delete mode 100644 python/deps/untypy/test/impl/test_callable.py delete mode 100644 python/deps/untypy/test/impl/test_dict.py delete mode 100644 python/deps/untypy/test/impl/test_forwardRef.py delete mode 100644 python/deps/untypy/test/impl/test_generator.py delete mode 100644 python/deps/untypy/test/impl/test_interface_dict.py delete mode 100644 python/deps/untypy/test/impl/test_interface_set.py delete mode 100644 python/deps/untypy/test/impl/test_list.py delete mode 100644 python/deps/untypy/test/impl/test_literal.py delete mode 100644 python/deps/untypy/test/impl/test_none.py delete mode 100644 python/deps/untypy/test/impl/test_optional.py delete mode 100644 python/deps/untypy/test/impl/test_protocol.py delete mode 100644 python/deps/untypy/test/impl/test_set.py delete mode 100644 python/deps/untypy/test/impl/test_simple.py delete mode 100644 python/deps/untypy/test/impl/test_str.py delete mode 100644 python/deps/untypy/test/impl/test_tuple.py delete mode 100644 python/deps/untypy/test/impl/test_union.py delete mode 100644 python/deps/untypy/test/patching_dummy/__init__.py delete mode 100644 python/deps/untypy/test/patching_dummy/a/__init__.py delete mode 100644 python/deps/untypy/test/patching_dummy/a/a_sub.py delete mode 100644 python/deps/untypy/test/patching_dummy/argument_types.py delete mode 100644 python/deps/untypy/test/patching_dummy/async_gen.py delete mode 100644 python/deps/untypy/test/patching_dummy/b/__init__.py delete mode 100644 python/deps/untypy/test/patching_dummy/b/b_sub.py delete mode 100644 python/deps/untypy/test/patching_dummy/c.py delete mode 100644 python/deps/untypy/test/patching_dummy/patching_classes.py delete mode 100644 python/deps/untypy/test/patching_dummy/patching_does_not_change_signature.py delete mode 100644 python/deps/untypy/test/patching_dummy/unpatched.py delete mode 100644 python/deps/untypy/test/test_patching.py delete mode 100644 python/deps/untypy/test/test_standalone_checker.py delete mode 100644 python/deps/untypy/test/util.py delete mode 100644 python/deps/untypy/test/util_test/__init__.py delete mode 100644 python/deps/untypy/test/util_test/test_return_traces.py delete mode 100644 python/deps/untypy/test/util_test/test_wrapper.py delete mode 100644 python/deps/untypy/test/util_test/untypy_test_case.py delete mode 100644 python/deps/untypy/untypy/__init__.py delete mode 100644 python/deps/untypy/untypy/error.py delete mode 100644 python/deps/untypy/untypy/impl/__init__.py delete mode 100644 python/deps/untypy/untypy/impl/alias.py delete mode 100644 python/deps/untypy/untypy/impl/annotated.py delete mode 100644 python/deps/untypy/untypy/impl/any.py delete mode 100644 python/deps/untypy/untypy/impl/callable.py delete mode 100644 python/deps/untypy/untypy/impl/choice.py delete mode 100644 python/deps/untypy/untypy/impl/dummy_delayed.py delete mode 100644 python/deps/untypy/untypy/impl/generator.py delete mode 100644 python/deps/untypy/untypy/impl/generic.py delete mode 100644 python/deps/untypy/untypy/impl/interface.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/__init__.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/dict.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/iterable.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/list.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/sequence.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/set.py delete mode 100644 python/deps/untypy/untypy/impl/interfaces/util.py delete mode 100644 python/deps/untypy/untypy/impl/literal.py delete mode 100644 python/deps/untypy/untypy/impl/none.py delete mode 100644 python/deps/untypy/untypy/impl/optional.py delete mode 100644 python/deps/untypy/untypy/impl/protocol.py delete mode 100644 python/deps/untypy/untypy/impl/simple.py delete mode 100644 python/deps/untypy/untypy/impl/string_forward_refs.py delete mode 100644 python/deps/untypy/untypy/impl/tuple.py delete mode 100644 python/deps/untypy/untypy/impl/union.py delete mode 100644 python/deps/untypy/untypy/interfaces.py delete mode 100644 python/deps/untypy/untypy/patching/__init__.py delete mode 100644 python/deps/untypy/untypy/patching/ast_transformer.py delete mode 100644 python/deps/untypy/untypy/patching/import_hook.py delete mode 100644 python/deps/untypy/untypy/patching/standalone_checker.py delete mode 100644 python/deps/untypy/untypy/util/__init__.py delete mode 100644 python/deps/untypy/untypy/util/condition.py delete mode 100644 python/deps/untypy/untypy/util/debug.py delete mode 100644 python/deps/untypy/untypy/util/display.py delete mode 100644 python/deps/untypy/untypy/util/return_traces.py delete mode 100644 python/deps/untypy/untypy/util/source_utils.py delete mode 100644 python/deps/untypy/untypy/util/tranformer_combinator.py delete mode 100644 python/deps/untypy/untypy/util/typedfunction.py delete mode 100644 python/deps/untypy/untypy/util/typehints.py delete mode 100644 python/deps/untypy/untypy/util/wrapper.py delete mode 120000 python/site-lib/untypy 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/examples/mylogfile b/python/deps/untypy/examples/mylogfile deleted file mode 100644 index e69de29b..00000000 diff --git a/python/deps/untypy/requirements.txt b/python/deps/untypy/requirements.txt deleted file mode 100644 index e69de29b..00000000 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-requirements.txt b/python/deps/untypy/test-requirements.txt deleted file mode 100644 index e69de29b..00000000 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/__init__.py b/python/deps/untypy/test/impl/__init__.py deleted file mode 100644 index e69de29b..00000000 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/__init__.py b/python/deps/untypy/test/patching_dummy/__init__.py deleted file mode 100644 index e69de29b..00000000 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/__init__.py b/python/deps/untypy/test/util_test/__init__.py deleted file mode 100644 index e69de29b..00000000 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/__init__.py b/python/deps/untypy/untypy/impl/interfaces/__init__.py deleted file mode 100644 index e69de29b..00000000 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 f1a5494f..00000000 --- a/python/deps/untypy/untypy/util/wrapper.py +++ /dev/null @@ -1,292 +0,0 @@ -import typing -import collections -import abc -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) - -_wrapperClasses = {} - -def wrapObj(wrapped, methods, name, extra): - k = wrapped.__class__ - if k in _wrapperClasses: - cls = _wrapperClasses[k] - else: - if isinstance(wrapped, abc.ABC): - class BaseWrapper(WrapperBase, wrapped.__class__, abc.ABC): - def __init__(self, wrapped): - self.__dict__ = wrapped.__dict__ - self.__wrapped__ = wrapped - _wrapperClasses[k] = BaseWrapper - cls = BaseWrapper - else: - class BaseWrapper(WrapperBase, wrapped.__class__): - def __init__(self, wrapped): - self.__dict__ = wrapped.__dict__ - self.__wrapped__ = wrapped - _wrapperClasses[k] = BaseWrapper - cls = BaseWrapper - if name is None: - name = 'ObjectWrapper' - if hasattr(wrapped, '__module__'): - mod = getattr(wrapped, '__module__') - else: - mod = None - return _wrap(wrapped, methods, mod, name, extra, cls) - -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/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 From 88427bfa90d2bf6e01525cea11e01708804040ef Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 27 May 2025 09:25:45 +0200 Subject: [PATCH 06/56] split file tests --- python/fileTests.py | 260 +---------------------------------------- python/fileTestsLib.py | 259 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 259 deletions(-) create mode 100644 python/fileTestsLib.py diff --git a/python/fileTests.py b/python/fileTests.py index 8781b620..e0e4ea79 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -1,262 +1,4 @@ -from dataclasses import dataclass -import os -from typing import * -import sys -import subprocess -import tempfile -import argparse -import re - -@dataclass(frozen=True) -class TestOpts: - cmd: str - baseDir: str - startAt: Optional[str] - only: Optional[str] - keepGoing: bool - -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") - - # Parse the arguments - args = parser.parse_args() - - scriptDir = os.path.dirname(os.path.abspath(__file__)) - return TestOpts( - cmd=f'{scriptDir}/src/runYourProgram.py', - baseDir=scriptDir, - startAt=args.start_at, - only=args.only, - keepGoing=args.keepGoing - ) - -TestStatus = Literal['passed', 'failed', 'skipped'] - -@dataclass -class TestResults: - passed: list[str] - failed: list[str] - skipped: list[str] - def record(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) -> str: - 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]) - return False - else: - return True - -def checkInstall(testFile: str, ctx: TestContext=globalCtx): - if shouldSkip(testFile, ctx, None): - ctx.results.record(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 "/[^"]*"', ' File ""', content, flags=re.MULTILINE) # Remove file paths - with open(filePath, 'w') as f: - f.write(content) - -def _check(testFile: str, - exitCode: int, - typecheck: bool, - args: list[str], - pythonPath: list[str], - minVersion: Optional[tuple[int, int]], - ctx: TestContext, - what: str) -> TestStatus: - if shouldSkip(testFile, ctx, minVersion): - return 'skipped' - - # Prepare expected output files - baseFile = os.path.splitext(testFile)[0] - expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) - expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) - - # Prepare the command - cmd = [sys.executable, ctx.opts.cmd, '--quiet'] - if not typecheck: - cmd.append('--no-typechecking') - cmd.append(testFile) - cmd.extend(args) - - with tempfile.TemporaryDirectory() as d: - actualStdoutFile = os.path.join(d, 'stdout.txt') - actualStderrFile = os.path.join(d, 'stderr.txt') - env = os.environ.copy() - env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) - 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' - - fixOutput(actualStdoutFile) - fixOutput(actualStderrFile) - - # Checkout outputs - if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): - return 'failed' - if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): - return 'failed' - - # If all checks pass - print(f"{testFile}{what} OK") - return 'passed' - -def check(testFile: str, - exitCode: int = 1, - typecheck: bool = True, - args: list[str] = [], - pythonPath: list[str] = [], - minVersion: Optional[tuple[int, int]] = None, - ctx: TestContext = globalCtx, - what: str = ''): - status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, ctx, what) - ctx.results.record(testFile, status) - if status == 'failed': - if not ctx.opts.keepGoing: - ctx.results.finish() - -def checkBoth(testFile: str, - exitCode: int = 1, - args: list[str] = [], - minVersion: Optional[tuple[int, int]] = None, - ctx: TestContext = globalCtx): - check(testFile, exitCode, typecheck=True, args=args, minVersion=minVersion, ctx=ctx, what=' (typecheck)') - check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') +from fileTestsLib import * checkInstall('test-data/fileWithImport.py') checkInstall('test-data/fileWithoutImport.py') diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py new file mode 100644 index 00000000..7f2c346c --- /dev/null +++ b/python/fileTestsLib.py @@ -0,0 +1,259 @@ +from dataclasses import dataclass +import os +from typing import * +import sys +import subprocess +import tempfile +import argparse +import re + +@dataclass(frozen=True) +class TestOpts: + cmd: str + baseDir: str + startAt: Optional[str] + only: Optional[str] + keepGoing: bool + +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") + + # Parse the arguments + args = parser.parse_args() + + scriptDir = os.path.dirname(os.path.abspath(__file__)) + return TestOpts( + cmd=f'{scriptDir}/src/runYourProgram.py', + baseDir=scriptDir, + startAt=args.start_at, + only=args.only, + keepGoing=args.keepGoing + ) + +TestStatus = Literal['passed', 'failed', 'skipped'] + +@dataclass +class TestResults: + passed: list[str] + failed: list[str] + skipped: list[str] + def record(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) -> str: + 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]) + return False + else: + return True + +def checkInstall(testFile: str, ctx: TestContext=globalCtx): + if shouldSkip(testFile, ctx, None): + ctx.results.record(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 "/[^"]*"', ' File ""', content, flags=re.MULTILINE) # Remove file paths + with open(filePath, 'w') as f: + f.write(content) + +def _check(testFile: str, + exitCode: int, + typecheck: bool, + args: list[str], + pythonPath: list[str], + minVersion: Optional[tuple[int, int]], + ctx: TestContext, + what: str) -> TestStatus: + if shouldSkip(testFile, ctx, minVersion): + return 'skipped' + + # Prepare expected output files + baseFile = os.path.splitext(testFile)[0] + expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) + expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) + + # Prepare the command + cmd = [sys.executable, ctx.opts.cmd, '--quiet'] + if not typecheck: + cmd.append('--no-typechecking') + cmd.append(testFile) + cmd.extend(args) + + with tempfile.TemporaryDirectory() as d: + actualStdoutFile = os.path.join(d, 'stdout.txt') + actualStderrFile = os.path.join(d, 'stderr.txt') + env = os.environ.copy() + env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) + 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' + + fixOutput(actualStdoutFile) + fixOutput(actualStderrFile) + + # Checkout outputs + if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): + return 'failed' + if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): + return 'failed' + + # If all checks pass + print(f"{testFile}{what} OK") + return 'passed' + +def check(testFile: str, + exitCode: int = 1, + typecheck: bool = True, + args: list[str] = [], + pythonPath: list[str] = [], + minVersion: Optional[tuple[int, int]] = None, + ctx: TestContext = globalCtx, + what: str = ''): + status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, ctx, what) + ctx.results.record(testFile, status) + if status == 'failed': + if not ctx.opts.keepGoing: + ctx.results.finish() + +def checkBoth(testFile: str, + exitCode: int = 1, + args: list[str] = [], + minVersion: Optional[tuple[int, int]] = None, + ctx: TestContext = globalCtx): + check(testFile, exitCode, typecheck=True, args=args, minVersion=minVersion, ctx=ctx, what=' (typecheck)') + check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') From 43822fd399194d9ba242dcc27719c1f5e7cb2773 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 27 May 2025 09:26:10 +0200 Subject: [PATCH 07/56] improve python tycheck IDE config --- python/.vscode/settings.json | 7 +++++++ python/pyrightconfig.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 python/.vscode/settings.json create mode 100644 python/pyrightconfig.json diff --git a/python/.vscode/settings.json b/python/.vscode/settings.json new file mode 100644 index 00000000..826290ef --- /dev/null +++ b/python/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.autoComplete.extraPaths": [ + "/Users/swehr/.vscode/extensions/stefanwehr.write-your-python-program-1.3.2/python/src/" + ], + "python.analysis.typeCheckingMode": "basic", + "python.analysis.diagnosticMode": "workspace", +} diff --git a/python/pyrightconfig.json b/python/pyrightconfig.json new file mode 100644 index 00000000..33081535 --- /dev/null +++ b/python/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "reportWildcardImportFromLibrary": false, + "reportMissingTypeStubs": false, + "exclude": ["test-data-2.0"], + // "ignore": ["src/asdl", "src/common/pretty.py"], + "typeCheckingMode": "basic" +} From 350b296acaee0ad5ea54aa5c2e89edb2a128aa97 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 4 Jun 2025 16:21:48 +0200 Subject: [PATCH 08/56] basics tests --- python/fileTests-2.0.py | 10 ++++++++++ python/fileTestsLib.py | 20 +++++++++++++++----- python/test-data-2.0/constructor.py | 5 +++++ python/test-data-2.0/constructor_ok.py | 6 ++++++ python/test-data-2.0/forwardRefs_ok.py | 15 +++++++++++++++ python/test-data-2.0/functionArg.py | 4 ++++ python/test-data-2.0/functionArg_ok.py | 5 +++++ python/test-data-2.0/functionNoResult.py | 4 ++++ python/test-data-2.0/functionNoResult2.py | 4 ++++ python/test-data-2.0/functionNoResult2_ok.py | 5 +++++ python/test-data-2.0/functionNoResult3.py | 4 ++++ python/test-data-2.0/functionNoResult3_ok.py | 5 +++++ python/test-data-2.0/functionNoResult_ok.py | 5 +++++ python/test-data-2.0/functionResult.py | 4 ++++ python/test-data-2.0/functionResult_ok.py | 5 +++++ python/test-data-2.0/iterator.py | 8 ++++++++ python/test-data-2.0/iterator_ok.py | 9 +++++++++ python/test-data-2.0/listArg.py | 4 ++++ python/test-data-2.0/listArg_ok.py | 5 +++++ python/test-data-2.0/listResult.py | 5 +++++ python/test-data-2.0/listResult_ok.py | 6 ++++++ python/test-data-2.0/method.py | 9 +++++++++ python/test-data-2.0/method_ok.py | 10 ++++++++++ python/test-data-2.0/partial.py | 4 ++++ python/test-data-2.0/record.py | 8 ++++++++ python/test-data-2.0/record_ok.py | 9 +++++++++ 26 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 python/fileTests-2.0.py create mode 100644 python/test-data-2.0/constructor.py create mode 100644 python/test-data-2.0/constructor_ok.py create mode 100644 python/test-data-2.0/forwardRefs_ok.py create mode 100644 python/test-data-2.0/functionArg.py create mode 100644 python/test-data-2.0/functionArg_ok.py create mode 100644 python/test-data-2.0/functionNoResult.py create mode 100644 python/test-data-2.0/functionNoResult2.py create mode 100644 python/test-data-2.0/functionNoResult2_ok.py create mode 100644 python/test-data-2.0/functionNoResult3.py create mode 100644 python/test-data-2.0/functionNoResult3_ok.py create mode 100644 python/test-data-2.0/functionNoResult_ok.py create mode 100644 python/test-data-2.0/functionResult.py create mode 100644 python/test-data-2.0/functionResult_ok.py create mode 100644 python/test-data-2.0/iterator.py create mode 100644 python/test-data-2.0/iterator_ok.py create mode 100644 python/test-data-2.0/listArg.py create mode 100644 python/test-data-2.0/listArg_ok.py create mode 100644 python/test-data-2.0/listResult.py create mode 100644 python/test-data-2.0/listResult_ok.py create mode 100644 python/test-data-2.0/method.py create mode 100644 python/test-data-2.0/method_ok.py create mode 100644 python/test-data-2.0/partial.py create mode 100644 python/test-data-2.0/record.py create mode 100644 python/test-data-2.0/record_ok.py diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py new file mode 100644 index 00000000..7e9abe5d --- /dev/null +++ b/python/fileTests-2.0.py @@ -0,0 +1,10 @@ +from pathlib import Path +from fileTestsLib import * + +directory = Path("test-data-2.0") + +for file in directory.iterdir(): + if file.is_file(): + checkBasic(file.as_posix()) + +globalCtx.results.finish() diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 7f2c346c..5a0c463d 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -185,6 +185,7 @@ def _check(testFile: str, args: list[str], pythonPath: list[str], minVersion: Optional[tuple[int, int]], + checkOutputs: bool, ctx: TestContext, what: str) -> TestStatus: if shouldSkip(testFile, ctx, minVersion): @@ -227,10 +228,11 @@ def _check(testFile: str, fixOutput(actualStderrFile) # Checkout outputs - if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): - return 'failed' - if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): - return 'failed' + if checkOutputs: + if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): + return 'failed' + if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): + return 'failed' # If all checks pass print(f"{testFile}{what} OK") @@ -242,9 +244,10 @@ def check(testFile: str, args: list[str] = [], pythonPath: list[str] = [], minVersion: Optional[tuple[int, int]] = None, + checkOutputs: bool = True, ctx: TestContext = globalCtx, what: str = ''): - status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, ctx, what) + status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, checkOutputs, ctx, what) ctx.results.record(testFile, status) if status == 'failed': if not ctx.opts.keepGoing: @@ -257,3 +260,10 @@ def checkBoth(testFile: str, ctx: TestContext = globalCtx): check(testFile, exitCode, typecheck=True, args=args, minVersion=minVersion, ctx=ctx, what=' (typecheck)') check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') + +def checkBasic(testFile: str, ctx: TestContext = globalCtx): + if testFile.endswith('_ok.py'): + expectedExitCode = 0 + else: + expectedExitCode = 1 + check(testFile, exitCode=expectedExitCode, checkOutputs=False, ctx=ctx) diff --git a/python/test-data-2.0/constructor.py b/python/test-data-2.0/constructor.py new file mode 100644 index 00000000..bd9ca484 --- /dev/null +++ b/python/test-data-2.0/constructor.py @@ -0,0 +1,5 @@ +class C: + def __init__(self, x: int): + self.x = x + +c = C("1") diff --git a/python/test-data-2.0/constructor_ok.py b/python/test-data-2.0/constructor_ok.py new file mode 100644 index 00000000..57132fce --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/forwardRefs_ok.py b/python/test-data-2.0/forwardRefs_ok.py new file mode 100644 index 00000000..c3f7fc81 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/functionArg.py b/python/test-data-2.0/functionArg.py new file mode 100644 index 00000000..75d82801 --- /dev/null +++ b/python/test-data-2.0/functionArg.py @@ -0,0 +1,4 @@ +def foo(i: int) -> int: + return i + 1 + +foo("1") diff --git a/python/test-data-2.0/functionArg_ok.py b/python/test-data-2.0/functionArg_ok.py new file mode 100644 index 00000000..9f86377b --- /dev/null +++ b/python/test-data-2.0/functionArg_ok.py @@ -0,0 +1,5 @@ +def foo(i: int) -> int: + return i + 1 + +foo(1) +print('ok') diff --git a/python/test-data-2.0/functionNoResult.py b/python/test-data-2.0/functionNoResult.py new file mode 100644 index 00000000..75085123 --- /dev/null +++ b/python/test-data-2.0/functionNoResult.py @@ -0,0 +1,4 @@ +def foo(i: int) -> None: + return "x" + +foo(1) diff --git a/python/test-data-2.0/functionNoResult2.py b/python/test-data-2.0/functionNoResult2.py new file mode 100644 index 00000000..167c5b3a --- /dev/null +++ b/python/test-data-2.0/functionNoResult2.py @@ -0,0 +1,4 @@ +def foo(i: int) -> int: + pass + +foo(1) diff --git a/python/test-data-2.0/functionNoResult2_ok.py b/python/test-data-2.0/functionNoResult2_ok.py new file mode 100644 index 00000000..5b658665 --- /dev/null +++ b/python/test-data-2.0/functionNoResult2_ok.py @@ -0,0 +1,5 @@ +def foo(i: int) -> int: + return 1 + +foo(1) +print('ok') diff --git a/python/test-data-2.0/functionNoResult3.py b/python/test-data-2.0/functionNoResult3.py new file mode 100644 index 00000000..bb14b13b --- /dev/null +++ b/python/test-data-2.0/functionNoResult3.py @@ -0,0 +1,4 @@ +def foo(i: int): + return "x" + +foo(1) diff --git a/python/test-data-2.0/functionNoResult3_ok.py b/python/test-data-2.0/functionNoResult3_ok.py new file mode 100644 index 00000000..c67b93a6 --- /dev/null +++ b/python/test-data-2.0/functionNoResult3_ok.py @@ -0,0 +1,5 @@ +def foo(i: int): + pass + +foo(1) +print('ok') diff --git a/python/test-data-2.0/functionNoResult_ok.py b/python/test-data-2.0/functionNoResult_ok.py new file mode 100644 index 00000000..c67b93a6 --- /dev/null +++ b/python/test-data-2.0/functionNoResult_ok.py @@ -0,0 +1,5 @@ +def foo(i: int): + pass + +foo(1) +print('ok') diff --git a/python/test-data-2.0/functionResult.py b/python/test-data-2.0/functionResult.py new file mode 100644 index 00000000..5f299e99 --- /dev/null +++ b/python/test-data-2.0/functionResult.py @@ -0,0 +1,4 @@ +def foo(i: int) -> int: + return "foo_" + str(i) + +foo(1) diff --git a/python/test-data-2.0/functionResult_ok.py b/python/test-data-2.0/functionResult_ok.py new file mode 100644 index 00000000..9f86377b --- /dev/null +++ b/python/test-data-2.0/functionResult_ok.py @@ -0,0 +1,5 @@ +def foo(i: int) -> int: + return i + 1 + +foo(1) +print('ok') diff --git a/python/test-data-2.0/iterator.py b/python/test-data-2.0/iterator.py new file mode 100644 index 00000000..cc0c7e4b --- /dev/null +++ b/python/test-data-2.0/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-2.0/iterator_ok.py b/python/test-data-2.0/iterator_ok.py new file mode 100644 index 00000000..8baf1cb4 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/listArg.py b/python/test-data-2.0/listArg.py new file mode 100644 index 00000000..2917bc3e --- /dev/null +++ b/python/test-data-2.0/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-2.0/listArg_ok.py b/python/test-data-2.0/listArg_ok.py new file mode 100644 index 00000000..b5edecae --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/listResult.py b/python/test-data-2.0/listResult.py new file mode 100644 index 00000000..c1797727 --- /dev/null +++ b/python/test-data-2.0/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-2.0/listResult_ok.py b/python/test-data-2.0/listResult_ok.py new file mode 100644 index 00000000..df81782e --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/method.py b/python/test-data-2.0/method.py new file mode 100644 index 00000000..5229e620 --- /dev/null +++ b/python/test-data-2.0/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-2.0/method_ok.py b/python/test-data-2.0/method_ok.py new file mode 100644 index 00000000..7b7d1546 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/partial.py b/python/test-data-2.0/partial.py new file mode 100644 index 00000000..5ed50475 --- /dev/null +++ b/python/test-data-2.0/partial.py @@ -0,0 +1,4 @@ +def foo(i: int, j) -> None: + pass + +foo(1, 2) diff --git a/python/test-data-2.0/record.py b/python/test-data-2.0/record.py new file mode 100644 index 00000000..5adf60f5 --- /dev/null +++ b/python/test-data-2.0/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-2.0/record_ok.py b/python/test-data-2.0/record_ok.py new file mode 100644 index 00000000..3b5eb905 --- /dev/null +++ b/python/test-data-2.0/record_ok.py @@ -0,0 +1,9 @@ +from wypp import * + +@record +class Person: + name: str + age: int + +p = Person("Alice", 30) +print('ok') From db1776a7d3d693d2dbd7cc077bd0506fcf847712 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 4 Jun 2025 16:23:03 +0200 Subject: [PATCH 09/56] TEMP --- python/TODO_nowrappers.md | 5 + python/pyrightconfig.json | 7 +- python/src/errors.py | 9 ++ python/src/myTypeguard.py | 16 +++ python/src/runYourProgram.py | 4 +- python/src/runner2.py | 28 +++++ python/src/runner3.py | 196 +++++++++++++++++++++++++++++++++ python/src/writeYourProgram.py | 11 +- 8 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 python/TODO_nowrappers.md create mode 100644 python/src/errors.py create mode 100644 python/src/myTypeguard.py create mode 100644 python/src/runner2.py create mode 100644 python/src/runner3.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md new file mode 100644 index 00000000..90e0d031 --- /dev/null +++ b/python/TODO_nowrappers.md @@ -0,0 +1,5 @@ +* support for records is missing +* partial annotations are supported, not flagged as an error. + * main problem: no return type annotation is not interprete as 'None' +* iterator broken +* check: caching in __pycache__ could hurt us when the instrumentation code changes diff --git a/python/pyrightconfig.json b/python/pyrightconfig.json index 33081535..812bdb0f 100644 --- a/python/pyrightconfig.json +++ b/python/pyrightconfig.json @@ -1,7 +1,10 @@ { + // See https://microsoft.github.io/pyright/#/configuration?id=pyright-configuration "reportWildcardImportFromLibrary": false, "reportMissingTypeStubs": false, - "exclude": ["test-data-2.0"], - // "ignore": ["src/asdl", "src/common/pretty.py"], + "exclude": ["test-data-2.0", "test-data", "trash"], + "ignore": ["src/typeguard", "site-lib"], + "extraPaths": ["src"], "typeCheckingMode": "basic" } + diff --git a/python/src/errors.py b/python/src/errors.py new file mode 100644 index 00000000..bddd2e9d --- /dev/null +++ b/python/src/errors.py @@ -0,0 +1,9 @@ +# DeliberateError instances are not reported as bugs +class DeliberateError: + pass + +class WyppTypeError(TypeError, DeliberateError): + pass + +class WyppAttributeError(AttributeError, DeliberateError): + pass diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py new file mode 100644 index 00000000..c5b5f5ef --- /dev/null +++ b/python/src/myTypeguard.py @@ -0,0 +1,16 @@ +# Wrapper module for typeguard. Do not import typeguard directly but always via myTypeguard + +from typing import Any +import typeguard.src.typeguard as typeguard + + +def matchesTy(a: Any, ty: Any) -> bool: + try: + typeguard.check_type(a, ty, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS) + return True + except typeguard.TypeCheckError as e: + return False + +def renderTy(t: Any) -> str: + return typeguard._utils.qualified_name(t) + diff --git a/python/src/runYourProgram.py b/python/src/runYourProgram.py index cd2dd616..515be54c 100644 --- a/python/src/runYourProgram.py +++ b/python/src/runYourProgram.py @@ -13,5 +13,5 @@ sys.exit(1) if __name__ == '__main__': - import runner - runner.main(globals()) + import runner3 as r + r.main(globals()) diff --git a/python/src/runner2.py b/python/src/runner2.py new file mode 100644 index 00000000..d97e6602 --- /dev/null +++ b/python/src/runner2.py @@ -0,0 +1,28 @@ +import os +import sys +import runner as r +import importlib +import runpy +LIB_DIR = os.path.dirname(__file__) +TYPEGUARD_DIR = os.path.join(LIB_DIR, "..", "deps", "typeguard", "src") +sys.path.insert(0, TYPEGUARD_DIR) + +import typeguard + +def main(globals, argList=None): + (args, restArgs) = r.parseCmdlineArgs(argList) + typeguard.config.collection_check_strategy = typeguard.CollectionCheckStrategy.ALL_ITEMS + with typeguard.install_import_hook(): + typeguard.config.debug_instrumentation = True + fileToRun = args.file + if args.changeDir: + os.chdir(os.path.dirname(fileToRun)) + fileToRun = os.path.basename(fileToRun) + modDir = os.path.dirname(fileToRun) + sys.path.insert(0, modDir) + modName = os.path.basename(os.path.splitext(fileToRun)[0]) + print('here') + # runpy.run_path(fileToRun, init_globals=globals, run_name=modName) + # __import__(modName) + importlib.import_module(modName) + # import iterator diff --git a/python/src/runner3.py b/python/src/runner3.py new file mode 100644 index 00000000..92e8cec4 --- /dev/null +++ b/python/src/runner3.py @@ -0,0 +1,196 @@ +from __future__ import annotations +import sys +import importlib.abc +import types +from collections.abc import Callable, Iterable, Sequence +from importlib.machinery import ModuleSpec, SourceFileLoader +from importlib.util import decode_source +import ast +from os import PathLike +import io +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 +from typing import TypeVar, Any +import inspect +import typing +import os +from dataclasses import dataclass + +from myTypeguard import matchesTy, renderTy + +def printVars(what: str, *l): + s = what + ": " + ', '.join([str(x) for x in l]) + print(s) + +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 isEmptyAnnotation(t: Any) -> bool: + return t is inspect.Signature.empty or t is inspect.Parameter.empty + +def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, cfg: CheckCfg) -> None: + params = list(sig.parameters) + if len(params) != len(args): + raise TypeError(f"Expected {len(params)} arguments, got {len(args)}") + for i in range(len(args)): + name = params[i] + t = sig.parameters[name].annotation + if isEmptyAnnotation(t): + if i == 0 and cfg.kind == 'method': + if name != 'self': + raise TypeError(f'Name of first parameter of method {name} must be self not {name}') + else: + raise TypeError(f'Missing type for parameter {name}') + else: + a = args[i] + if not matchesTy(a, t): + raise TypeError(f'Expected argument of type {renderTy(t)} for parameter {name}, got {renderTy(type(a))}: {a}') + +def checkReturn(sig: inspect.Signature, result: Any) -> None: + t = sig.return_annotation + if isEmptyAnnotation(t): + t = None + if not matchesTy(result, t): + raise TypeError(f"Expected return type {renderTy(t)}, got {renderTy(type(result))}: {result}") + +FunKind = typing.Literal['function', 'method', 'staticmethod'] +@dataclass +class CheckCfg: + kind: FunKind + @staticmethod + def fromDict(d: dict) -> CheckCfg: + return CheckCfg(kind=d['kind']) + +def wrap(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: + checkCfg = CheckCfg.fromDict(cfg) + def _wrap(f: Callable[P, T]) -> Callable[P, T]: + sig = inspect.signature(f) + def wrapped(*args, **kwargs) -> T: + checkArguments(sig, args, kwargs, checkCfg) + result = _call_with_frames_removed(f, *args, **kwargs) + checkReturn(sig, result) + return result + return wrapped + return _wrap + +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'}") + methodConfig: ast.expr = parseExp("{'kind': 'method'}") + +def transformStmt(stmt: ast.stmt, insideClass: bool) -> ast.stmt: + # FIXME: static methods + cfg = Configs.methodConfig if insideClass else Configs.funConfig + wrapExp = ast.Call(ast.Name(id='wrap', ctx=ast.Load()), [cfg], []) + match stmt: + case ast.FunctionDef(name, args, body, decorators, returns, tyComment, tyParams): + return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) + case ast.ClassDef(name, bases, keywords, body, decorator_list, type_params): + newBody = [transformStmt(s, insideClass=True) for s in body] + return ast.ClassDef(name, bases, keywords, newBody, decorator_list, type_params) + case _: + return stmt + +def transformModule(m: ast.Module | ast.Expression | ast.Interactive) -> ast.Module | ast.Expression | ast.Interactive: + match m: + case ast.Module(body, type_ignores): + newStmts = [transformStmt(stmt, insideClass=False) for stmt in body] + 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 = _call_with_frames_removed(ast.parse, source, path, "exec") + tree = transformModule(tree) + ast.fix_missing_locations(tree) + + print( + f"Source code of {path!r} after instrumentation:\n" + "----------------------------------------------", + file=sys.stderr, + ) + print(ast.unparse(tree), file=sys.stderr) + print("----------------------------------------------", file=sys.stderr) + + code = _call_with_frames_removed(compile, tree, path, "exec", 0, dont_inherit=True) + return code + +class InstrumentingFinder(importlib.abc.MetaPathFinder): + def __init__(self, finder, modDir): + self._origFinder = finder + self.modDir = os.path.realpath(modDir) + '/' + + 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) + origin = os.path.realpath(spec.origin) + isLocalModule = origin.startswith(self.modDir) + if spec and spec.loader and isLocalModule: + spec.loader = InstrumentingLoader(spec.loader.name, spec.loader.path) + return spec + +def setupFinder(modDir: str): + 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") + sys.meta_path.insert(0, InstrumentingFinder(finder, modDir)) + +import runner as r +import os +import runpy +def main(globals, argList=None): + print('runner3') + (args, restArgs) = r.parseCmdlineArgs(argList) + fileToRun = args.file + if args.changeDir: + os.chdir(os.path.dirname(fileToRun)) + fileToRun = os.path.basename(fileToRun) + modDir = os.path.dirname(fileToRun) + setupFinder(modDir) + sys.path.insert(0, modDir) + modName = os.path.basename(os.path.splitext(fileToRun)[0]) + globals['wrap'] = wrap + runpy.run_module(modName, init_globals=globals, run_name=modName) + # __import__(modName) + # importlib.import_module(modName) + # import iterator diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index ce22370f..43185343 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -3,7 +3,8 @@ import typing import dataclasses import inspect -import types +import myTypeguard +import errors _DEBUG = False def _debug(s): @@ -28,8 +29,6 @@ def _debug(s): dataclass = dataclasses.dataclass -unchecked = untypy.unchecked - intPositive = typing.Annotated[int, lambda i: i > 0, 'intPositive'] nat = typing.Annotated[int, lambda i: i >= 0, 'nat'] intNonNegative = typing.Annotated[int, lambda i: i >= 0, 'intNonNegative'] @@ -78,8 +77,8 @@ def formatArg(x): 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}]?") + argStr = ', '.join([formatArg(myTypeguard.renderTy(x)) for x in args]) + raise errors.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. @@ -134,7 +133,7 @@ def _setattr(obj, k, v): elif k in fields: oldSetattr(obj, k, v) else: - raise untypy.error.WyppAttributeError(f'Unknown attribute {k} for record {cls.__name__}') + raise errors.WyppAttributeError(f'Unknown attribute {k} for record {cls.__name__}') setattr(cls, "__setattr__", _setattr) return cls From 808ffc16ba37fc82e85386842728d4de2afe4dfd Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 5 Jun 2025 18:02:41 +0200 Subject: [PATCH 10/56] add typeguard Commit: 8ea024a007bb279f286038e54ebecbbcf8c48b10 Repo: github.com:skogsbaer/typeguard.git --- python/src/typeguard/__init__.py | 48 + python/src/typeguard/_checkers.py | 1080 +++++++++++++++++ python/src/typeguard/_config.py | 106 ++ python/src/typeguard/_decorators.py | 239 ++++ python/src/typeguard/_exceptions.py | 42 + python/src/typeguard/_functions.py | 303 +++++ python/src/typeguard/_importhook.py | 213 ++++ python/src/typeguard/_memo.py | 48 + python/src/typeguard/_pytest_plugin.py | 127 ++ python/src/typeguard/_suppression.py | 86 ++ python/src/typeguard/_transformer.py | 1228 ++++++++++++++++++++ python/src/typeguard/_union_transformer.py | 43 + python/src/typeguard/_utils.py | 169 +++ python/src/typeguard/py.typed | 0 14 files changed, 3732 insertions(+) create mode 100644 python/src/typeguard/__init__.py create mode 100644 python/src/typeguard/_checkers.py create mode 100644 python/src/typeguard/_config.py create mode 100644 python/src/typeguard/_decorators.py create mode 100644 python/src/typeguard/_exceptions.py create mode 100644 python/src/typeguard/_functions.py create mode 100644 python/src/typeguard/_importhook.py create mode 100644 python/src/typeguard/_memo.py create mode 100644 python/src/typeguard/_pytest_plugin.py create mode 100644 python/src/typeguard/_suppression.py create mode 100644 python/src/typeguard/_transformer.py create mode 100644 python/src/typeguard/_union_transformer.py create mode 100644 python/src/typeguard/_utils.py create mode 100644 python/src/typeguard/py.typed diff --git a/python/src/typeguard/__init__.py b/python/src/typeguard/__init__.py new file mode 100644 index 00000000..6781cad0 --- /dev/null +++ b/python/src/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/src/typeguard/_checkers.py b/python/src/typeguard/_checkers.py new file mode 100644 index 00000000..eb6ac878 --- /dev/null +++ b/python/src/typeguard/_checkers.py @@ -0,0 +1,1080 @@ +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 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 + """ + + 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 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/src/typeguard/_config.py b/python/src/typeguard/_config.py new file mode 100644 index 00000000..c3097640 --- /dev/null +++ b/python/src/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/src/typeguard/_decorators.py b/python/src/typeguard/_decorators.py new file mode 100644 index 00000000..3ed85b0c --- /dev/null +++ b/python/src/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/src/typeguard/_exceptions.py b/python/src/typeguard/_exceptions.py new file mode 100644 index 00000000..625437a6 --- /dev/null +++ b/python/src/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/src/typeguard/_functions.py b/python/src/typeguard/_functions.py new file mode 100644 index 00000000..ca21c14c --- /dev/null +++ b/python/src/typeguard/_functions.py @@ -0,0 +1,303 @@ +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 = ..., +) -> T: ... + + +@overload +def check_type( + value: object, + expected_type: Any, + *, + forward_ref_policy: ForwardRefPolicy = ..., + typecheck_fail_callback: TypeCheckFailCallback | None = ..., + collection_check_strategy: CollectionCheckStrategy = ..., +) -> 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 + ), +) -> 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 + + frame = sys._getframe(1) + memo = TypeCheckMemo(frame.f_globals, frame.f_locals, config=config) + 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/src/typeguard/_importhook.py b/python/src/typeguard/_importhook.py new file mode 100644 index 00000000..0d1c6274 --- /dev/null +++ b/python/src/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/src/typeguard/_memo.py b/python/src/typeguard/_memo.py new file mode 100644 index 00000000..4c97588c --- /dev/null +++ b/python/src/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/src/typeguard/_pytest_plugin.py b/python/src/typeguard/_pytest_plugin.py new file mode 100644 index 00000000..7b2f494e --- /dev/null +++ b/python/src/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/src/typeguard/_suppression.py b/python/src/typeguard/_suppression.py new file mode 100644 index 00000000..bbbfbfbe --- /dev/null +++ b/python/src/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/src/typeguard/_transformer.py b/python/src/typeguard/_transformer.py new file mode 100644 index 00000000..7b6dda85 --- /dev/null +++ b/python/src/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/src/typeguard/_union_transformer.py b/python/src/typeguard/_union_transformer.py new file mode 100644 index 00000000..1c296d35 --- /dev/null +++ b/python/src/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/src/typeguard/_utils.py b/python/src/typeguard/_utils.py new file mode 100644 index 00000000..f61b94d2 --- /dev/null +++ b/python/src/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) -> 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"): + 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/src/typeguard/py.typed b/python/src/typeguard/py.typed new file mode 100644 index 00000000..e69de29b From 41dfa132619c3189e1c0b353f1411c380d3de5c2 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 6 Jun 2025 08:19:22 +0200 Subject: [PATCH 11/56] move typeguard --- python/{src => deps}/typeguard/__init__.py | 0 python/{src => deps}/typeguard/_checkers.py | 0 python/{src => deps}/typeguard/_config.py | 0 python/{src => deps}/typeguard/_decorators.py | 0 python/{src => deps}/typeguard/_exceptions.py | 0 python/{src => deps}/typeguard/_functions.py | 0 python/{src => deps}/typeguard/_importhook.py | 0 python/{src => deps}/typeguard/_memo.py | 0 python/{src => deps}/typeguard/_pytest_plugin.py | 0 python/{src => deps}/typeguard/_suppression.py | 0 python/{src => deps}/typeguard/_transformer.py | 0 python/{src => deps}/typeguard/_union_transformer.py | 0 python/{src => deps}/typeguard/_utils.py | 0 python/{src => deps}/typeguard/py.typed | 0 python/site-lib/typeguard | 1 + 15 files changed, 1 insertion(+) rename python/{src => deps}/typeguard/__init__.py (100%) rename python/{src => deps}/typeguard/_checkers.py (100%) rename python/{src => deps}/typeguard/_config.py (100%) rename python/{src => deps}/typeguard/_decorators.py (100%) rename python/{src => deps}/typeguard/_exceptions.py (100%) rename python/{src => deps}/typeguard/_functions.py (100%) rename python/{src => deps}/typeguard/_importhook.py (100%) rename python/{src => deps}/typeguard/_memo.py (100%) rename python/{src => deps}/typeguard/_pytest_plugin.py (100%) rename python/{src => deps}/typeguard/_suppression.py (100%) rename python/{src => deps}/typeguard/_transformer.py (100%) rename python/{src => deps}/typeguard/_union_transformer.py (100%) rename python/{src => deps}/typeguard/_utils.py (100%) rename python/{src => deps}/typeguard/py.typed (100%) create mode 120000 python/site-lib/typeguard diff --git a/python/src/typeguard/__init__.py b/python/deps/typeguard/__init__.py similarity index 100% rename from python/src/typeguard/__init__.py rename to python/deps/typeguard/__init__.py diff --git a/python/src/typeguard/_checkers.py b/python/deps/typeguard/_checkers.py similarity index 100% rename from python/src/typeguard/_checkers.py rename to python/deps/typeguard/_checkers.py diff --git a/python/src/typeguard/_config.py b/python/deps/typeguard/_config.py similarity index 100% rename from python/src/typeguard/_config.py rename to python/deps/typeguard/_config.py diff --git a/python/src/typeguard/_decorators.py b/python/deps/typeguard/_decorators.py similarity index 100% rename from python/src/typeguard/_decorators.py rename to python/deps/typeguard/_decorators.py diff --git a/python/src/typeguard/_exceptions.py b/python/deps/typeguard/_exceptions.py similarity index 100% rename from python/src/typeguard/_exceptions.py rename to python/deps/typeguard/_exceptions.py diff --git a/python/src/typeguard/_functions.py b/python/deps/typeguard/_functions.py similarity index 100% rename from python/src/typeguard/_functions.py rename to python/deps/typeguard/_functions.py diff --git a/python/src/typeguard/_importhook.py b/python/deps/typeguard/_importhook.py similarity index 100% rename from python/src/typeguard/_importhook.py rename to python/deps/typeguard/_importhook.py diff --git a/python/src/typeguard/_memo.py b/python/deps/typeguard/_memo.py similarity index 100% rename from python/src/typeguard/_memo.py rename to python/deps/typeguard/_memo.py diff --git a/python/src/typeguard/_pytest_plugin.py b/python/deps/typeguard/_pytest_plugin.py similarity index 100% rename from python/src/typeguard/_pytest_plugin.py rename to python/deps/typeguard/_pytest_plugin.py diff --git a/python/src/typeguard/_suppression.py b/python/deps/typeguard/_suppression.py similarity index 100% rename from python/src/typeguard/_suppression.py rename to python/deps/typeguard/_suppression.py diff --git a/python/src/typeguard/_transformer.py b/python/deps/typeguard/_transformer.py similarity index 100% rename from python/src/typeguard/_transformer.py rename to python/deps/typeguard/_transformer.py diff --git a/python/src/typeguard/_union_transformer.py b/python/deps/typeguard/_union_transformer.py similarity index 100% rename from python/src/typeguard/_union_transformer.py rename to python/deps/typeguard/_union_transformer.py diff --git a/python/src/typeguard/_utils.py b/python/deps/typeguard/_utils.py similarity index 100% rename from python/src/typeguard/_utils.py rename to python/deps/typeguard/_utils.py diff --git a/python/src/typeguard/py.typed b/python/deps/typeguard/py.typed similarity index 100% rename from python/src/typeguard/py.typed rename to python/deps/typeguard/py.typed diff --git a/python/site-lib/typeguard b/python/site-lib/typeguard new file mode 120000 index 00000000..d3949e20 --- /dev/null +++ b/python/site-lib/typeguard @@ -0,0 +1 @@ +../deps/typeguard/ \ No newline at end of file From eb2e7bab8e09badb3e649afc027c9883d400aea8 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 6 Jun 2025 08:19:41 +0200 Subject: [PATCH 12/56] add typing_extensions --- python/deps/typing_extensions.py | 4244 ++++++++++++++++++++++++++ python/site-lib/typing_extensions.py | 1 + 2 files changed, 4245 insertions(+) create mode 100644 python/deps/typing_extensions.py create mode 120000 python/site-lib/typing_extensions.py diff --git a/python/deps/typing_extensions.py b/python/deps/typing_extensions.py new file mode 100644 index 00000000..efa09d55 --- /dev/null +++ b/python/deps/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/site-lib/typing_extensions.py b/python/site-lib/typing_extensions.py new file mode 120000 index 00000000..0e27af76 --- /dev/null +++ b/python/site-lib/typing_extensions.py @@ -0,0 +1 @@ +../deps/typing_extensions.py \ No newline at end of file From 81632bc720e8840242ddc8c178a3d887a4db8c6e Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 1 Jul 2025 12:04:29 +0200 Subject: [PATCH 13/56] progress --- python/TODO.org | 2 + python/pyrightconfig.json | 2 +- python/run | 4 +- python/setup_new.py | 42 ++ python/site-lib/README | 4 +- python/src/__init__.py | 2 - python/src/errors.py | 25 +- python/src/instrument.py | 102 ++++ python/src/myLogging.py | 20 + python/src/myTypeguard.py | 6 +- python/src/runner.py | 41 +- python/src/runner2.py | 28 - python/src/runner3.py | 186 +------ python/src/typecheck.py | 65 +++ python/src/utils.py | 21 + python/src/writeYourProgram.py | 32 +- python/test-data-2.0/iterator.err | 13 + python/test-data/testLiteralInstanceOf.out | 3 +- python/test-data/testLiteralInstanceOf.py | 3 +- python/trash/untypy/.gitignore | 1 + python/trash/untypy/LICENSE | 21 + python/trash/untypy/README.rst | 43 ++ python/trash/untypy/examples/ex01.py | 18 + python/trash/untypy/examples/ex02.py | 26 + python/trash/untypy/examples/ex03.py | 14 + python/trash/untypy/examples/ex04.py | 17 + python/trash/untypy/examples/ex05.py | 30 + python/trash/untypy/examples/ex06.py | 15 + python/trash/untypy/examples/ex09.py | 11 + python/trash/untypy/examples/ex10.py | 17 + python/trash/untypy/examples/mylogfile | 0 python/trash/untypy/requirements.txt | 0 python/trash/untypy/runtests.sh | 4 + python/trash/untypy/setup.py | 12 + python/trash/untypy/test-requirements.txt | 0 python/trash/untypy/test/__init__.py | 3 + python/trash/untypy/test/impl/__init__.py | 0 python/trash/untypy/test/impl/test_any.py | 15 + .../trash/untypy/test/impl/test_callable.py | 78 +++ python/trash/untypy/test/impl/test_dict.py | 87 +++ .../trash/untypy/test/impl/test_forwardRef.py | 57 ++ .../trash/untypy/test/impl/test_generator.py | 167 ++++++ .../untypy/test/impl/test_interface_dict.py | 235 ++++++++ .../untypy/test/impl/test_interface_set.py | 90 +++ python/trash/untypy/test/impl/test_list.py | 348 ++++++++++++ python/trash/untypy/test/impl/test_literal.py | 29 + python/trash/untypy/test/impl/test_none.py | 35 ++ .../trash/untypy/test/impl/test_optional.py | 49 ++ .../trash/untypy/test/impl/test_protocol.py | 280 ++++++++++ python/trash/untypy/test/impl/test_set.py | 171 ++++++ python/trash/untypy/test/impl/test_simple.py | 160 ++++++ python/trash/untypy/test/impl/test_str.py | 96 ++++ python/trash/untypy/test/impl/test_tuple.py | 156 ++++++ python/trash/untypy/test/impl/test_union.py | 71 +++ .../untypy/test/patching_dummy/__init__.py | 0 .../untypy/test/patching_dummy/a/__init__.py | 5 + .../untypy/test/patching_dummy/a/a_sub.py | 2 + .../test/patching_dummy/argument_types.py | 9 + .../untypy/test/patching_dummy/async_gen.py | 16 + .../untypy/test/patching_dummy/b/__init__.py | 5 + .../untypy/test/patching_dummy/b/b_sub.py | 2 + python/trash/untypy/test/patching_dummy/c.py | 5 + .../test/patching_dummy/patching_classes.py | 6 + .../patching_does_not_change_signature.py | 7 + .../untypy/test/patching_dummy/unpatched.py | 2 + python/trash/untypy/test/test_patching.py | 24 + .../untypy/test/test_standalone_checker.py | 21 + python/trash/untypy/test/util.py | 35 ++ .../trash/untypy/test/util_test/__init__.py | 0 .../test/util_test/test_return_traces.py | 36 ++ .../untypy/test/util_test/test_wrapper.py | 170 ++++++ .../untypy/test/util_test/untypy_test_case.py | 30 + python/trash/untypy/untypy/__init__.py | 187 +++++++ python/trash/untypy/untypy/error.py | 384 +++++++++++++ python/trash/untypy/untypy/impl/__init__.py | 108 ++++ python/trash/untypy/untypy/impl/alias.py | 12 + python/trash/untypy/untypy/impl/annotated.py | 137 +++++ python/trash/untypy/untypy/impl/any.py | 34 ++ python/trash/untypy/untypy/impl/callable.py | 277 +++++++++ python/trash/untypy/untypy/impl/choice.py | 66 +++ .../trash/untypy/untypy/impl/dummy_delayed.py | 43 ++ python/trash/untypy/untypy/impl/generator.py | 128 +++++ python/trash/untypy/untypy/impl/generic.py | 98 ++++ python/trash/untypy/untypy/impl/interface.py | 87 +++ .../untypy/untypy/impl/interfaces/__init__.py | 0 .../untypy/untypy/impl/interfaces/dict.py | 68 +++ .../untypy/untypy/impl/interfaces/iterable.py | 25 + .../untypy/untypy/impl/interfaces/list.py | 56 ++ .../untypy/untypy/impl/interfaces/sequence.py | 31 ++ .../untypy/untypy/impl/interfaces/set.py | 53 ++ .../untypy/untypy/impl/interfaces/util.py | 6 + python/trash/untypy/untypy/impl/literal.py | 36 ++ python/trash/untypy/untypy/impl/none.py | 27 + python/trash/untypy/untypy/impl/optional.py | 44 ++ python/trash/untypy/untypy/impl/protocol.py | 524 ++++++++++++++++++ python/trash/untypy/untypy/impl/simple.py | 68 +++ .../untypy/untypy/impl/string_forward_refs.py | 19 + python/trash/untypy/untypy/impl/tuple.py | 103 ++++ python/trash/untypy/untypy/impl/union.py | 80 +++ python/trash/untypy/untypy/interfaces.py | 105 ++++ .../trash/untypy/untypy/patching/__init__.py | 59 ++ .../untypy/untypy/patching/ast_transformer.py | 165 ++++++ .../untypy/untypy/patching/import_hook.py | 65 +++ .../untypy/patching/standalone_checker.py | 59 ++ python/trash/untypy/untypy/util/__init__.py | 260 +++++++++ python/trash/untypy/untypy/util/condition.py | 130 +++++ python/trash/untypy/untypy/util/debug.py | 24 + python/trash/untypy/untypy/util/display.py | 44 ++ .../trash/untypy/untypy/util/return_traces.py | 75 +++ .../trash/untypy/untypy/util/source_utils.py | 36 ++ .../untypy/util/tranformer_combinator.py | 11 + .../trash/untypy/untypy/util/typedfunction.py | 218 ++++++++ python/trash/untypy/untypy/util/typehints.py | 133 +++++ python/trash/untypy/untypy/util/wrapper.py | 292 ++++++++++ 114 files changed, 7446 insertions(+), 263 deletions(-) create mode 100644 python/TODO.org create mode 100644 python/setup_new.py create mode 100644 python/src/instrument.py create mode 100644 python/src/myLogging.py delete mode 100644 python/src/runner2.py create mode 100644 python/src/typecheck.py create mode 100644 python/src/utils.py create mode 100644 python/test-data-2.0/iterator.err create mode 100644 python/trash/untypy/.gitignore create mode 100644 python/trash/untypy/LICENSE create mode 100644 python/trash/untypy/README.rst create mode 100644 python/trash/untypy/examples/ex01.py create mode 100644 python/trash/untypy/examples/ex02.py create mode 100644 python/trash/untypy/examples/ex03.py create mode 100644 python/trash/untypy/examples/ex04.py create mode 100644 python/trash/untypy/examples/ex05.py create mode 100644 python/trash/untypy/examples/ex06.py create mode 100644 python/trash/untypy/examples/ex09.py create mode 100644 python/trash/untypy/examples/ex10.py create mode 100644 python/trash/untypy/examples/mylogfile create mode 100644 python/trash/untypy/requirements.txt create mode 100755 python/trash/untypy/runtests.sh create mode 100644 python/trash/untypy/setup.py create mode 100644 python/trash/untypy/test-requirements.txt create mode 100644 python/trash/untypy/test/__init__.py create mode 100644 python/trash/untypy/test/impl/__init__.py create mode 100644 python/trash/untypy/test/impl/test_any.py create mode 100644 python/trash/untypy/test/impl/test_callable.py create mode 100644 python/trash/untypy/test/impl/test_dict.py create mode 100644 python/trash/untypy/test/impl/test_forwardRef.py create mode 100644 python/trash/untypy/test/impl/test_generator.py create mode 100644 python/trash/untypy/test/impl/test_interface_dict.py create mode 100644 python/trash/untypy/test/impl/test_interface_set.py create mode 100644 python/trash/untypy/test/impl/test_list.py create mode 100644 python/trash/untypy/test/impl/test_literal.py create mode 100644 python/trash/untypy/test/impl/test_none.py create mode 100644 python/trash/untypy/test/impl/test_optional.py create mode 100644 python/trash/untypy/test/impl/test_protocol.py create mode 100644 python/trash/untypy/test/impl/test_set.py create mode 100644 python/trash/untypy/test/impl/test_simple.py create mode 100644 python/trash/untypy/test/impl/test_str.py create mode 100644 python/trash/untypy/test/impl/test_tuple.py create mode 100644 python/trash/untypy/test/impl/test_union.py create mode 100644 python/trash/untypy/test/patching_dummy/__init__.py create mode 100644 python/trash/untypy/test/patching_dummy/a/__init__.py create mode 100644 python/trash/untypy/test/patching_dummy/a/a_sub.py create mode 100644 python/trash/untypy/test/patching_dummy/argument_types.py create mode 100644 python/trash/untypy/test/patching_dummy/async_gen.py create mode 100644 python/trash/untypy/test/patching_dummy/b/__init__.py create mode 100644 python/trash/untypy/test/patching_dummy/b/b_sub.py create mode 100644 python/trash/untypy/test/patching_dummy/c.py create mode 100644 python/trash/untypy/test/patching_dummy/patching_classes.py create mode 100644 python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py create mode 100644 python/trash/untypy/test/patching_dummy/unpatched.py create mode 100644 python/trash/untypy/test/test_patching.py create mode 100644 python/trash/untypy/test/test_standalone_checker.py create mode 100644 python/trash/untypy/test/util.py create mode 100644 python/trash/untypy/test/util_test/__init__.py create mode 100644 python/trash/untypy/test/util_test/test_return_traces.py create mode 100644 python/trash/untypy/test/util_test/test_wrapper.py create mode 100644 python/trash/untypy/test/util_test/untypy_test_case.py create mode 100644 python/trash/untypy/untypy/__init__.py create mode 100644 python/trash/untypy/untypy/error.py create mode 100644 python/trash/untypy/untypy/impl/__init__.py create mode 100644 python/trash/untypy/untypy/impl/alias.py create mode 100644 python/trash/untypy/untypy/impl/annotated.py create mode 100644 python/trash/untypy/untypy/impl/any.py create mode 100644 python/trash/untypy/untypy/impl/callable.py create mode 100644 python/trash/untypy/untypy/impl/choice.py create mode 100644 python/trash/untypy/untypy/impl/dummy_delayed.py create mode 100644 python/trash/untypy/untypy/impl/generator.py create mode 100644 python/trash/untypy/untypy/impl/generic.py create mode 100644 python/trash/untypy/untypy/impl/interface.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/__init__.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/dict.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/iterable.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/list.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/sequence.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/set.py create mode 100644 python/trash/untypy/untypy/impl/interfaces/util.py create mode 100644 python/trash/untypy/untypy/impl/literal.py create mode 100644 python/trash/untypy/untypy/impl/none.py create mode 100644 python/trash/untypy/untypy/impl/optional.py create mode 100644 python/trash/untypy/untypy/impl/protocol.py create mode 100644 python/trash/untypy/untypy/impl/simple.py create mode 100644 python/trash/untypy/untypy/impl/string_forward_refs.py create mode 100644 python/trash/untypy/untypy/impl/tuple.py create mode 100644 python/trash/untypy/untypy/impl/union.py create mode 100644 python/trash/untypy/untypy/interfaces.py create mode 100644 python/trash/untypy/untypy/patching/__init__.py create mode 100644 python/trash/untypy/untypy/patching/ast_transformer.py create mode 100644 python/trash/untypy/untypy/patching/import_hook.py create mode 100644 python/trash/untypy/untypy/patching/standalone_checker.py create mode 100644 python/trash/untypy/untypy/util/__init__.py create mode 100644 python/trash/untypy/untypy/util/condition.py create mode 100644 python/trash/untypy/untypy/util/debug.py create mode 100644 python/trash/untypy/untypy/util/display.py create mode 100644 python/trash/untypy/untypy/util/return_traces.py create mode 100644 python/trash/untypy/untypy/util/source_utils.py create mode 100644 python/trash/untypy/untypy/util/tranformer_combinator.py create mode 100644 python/trash/untypy/untypy/util/typedfunction.py create mode 100644 python/trash/untypy/untypy/util/typehints.py create mode 100644 python/trash/untypy/untypy/util/wrapper.py 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/pyrightconfig.json b/python/pyrightconfig.json index 812bdb0f..a979e183 100644 --- a/python/pyrightconfig.json +++ b/python/pyrightconfig.json @@ -3,7 +3,7 @@ "reportWildcardImportFromLibrary": false, "reportMissingTypeStubs": false, "exclude": ["test-data-2.0", "test-data", "trash"], - "ignore": ["src/typeguard", "site-lib"], + "ignore": ["deps", "site-lib"], "extraPaths": ["src"], "typeCheckingMode": "basic" } diff --git a/python/run b/python/run index e61be5c5..b2aa91ab 100755 --- a/python/run +++ b/python/run @@ -1,6 +1,8 @@ #!/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" diff --git a/python/setup_new.py b/python/setup_new.py new file mode 100644 index 00000000..b0a43537 --- /dev/null +++ b/python/setup_new.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages +import os +import json + +TOP_DIR = os.path.join(os.path.dirname(__file__), '..') +VERSION_FILE = 'VERSION' + +def writeVersionFile(): + pkgJson = os.path.join(TOP_DIR, 'package.json') + if os.path.isfile(pkgJson): + with open(pkgJson) as f: + d = json.load(f) + version = d['version'] + with open(VERSION_FILE, 'w') as f: + f.write(version) + +writeVersionFile() + +def readVersion(): + with open(VERSION_FILE) as f: + return f.read().strip() + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name='wypp', + version=readVersion(), + description='A user-friendly python programming environment for beginners', + long_description=long_description, + long_description_content_type="text/markdown", + 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'] + find_packages("deps/untypy", exclude=['test', 'test.*']), + python_requires='>=3.12.0', + scripts=['wypp'] +) + diff --git a/python/site-lib/README b/python/site-lib/README index d2769afc..c2e4f4a6 100644 --- a/python/site-lib/README +++ b/python/site-lib/README @@ -1,3 +1,3 @@ -This directory contains symlinks for wypp and untypy. +This directory contains symlinks for wypp, typeguard and typing_extensions. -Add this directory to PYTHONPATH if you want to import wypp and untypy in-place. +Add this directory to PYTHONPATH if you want to import these modules in-place. diff --git a/python/src/__init__.py b/python/src/__init__.py index 5117affb..c55843d2 100644 --- a/python/src/__init__.py +++ b/python/src/__init__.py @@ -30,7 +30,6 @@ T = w.T todo = w.todo U = w.U -unchecked = w.unchecked V = w.V __all__ = [ @@ -63,7 +62,6 @@ 'T', 'todo', 'U', - 'unchecked', 'V' ] diff --git a/python/src/errors.py b/python/src/errors.py index bddd2e9d..123cfa3b 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -1,9 +1,28 @@ +from typing import * +from dataclasses import dataclass + +@dataclass +class Pos: + line: int + col: int + +@dataclass +class Location: + file: str + startPos: Pos + endPos: Pos + +class WyppError: + pass + # DeliberateError instances are not reported as bugs -class DeliberateError: +class DeliberateError(WyppError): pass -class WyppTypeError(TypeError, DeliberateError): +class WyppTypeError(TypeError, DeliberateError, WyppError): + + # def __init__(self, expectedTy: Any, givenValue: Any) pass -class WyppAttributeError(AttributeError, DeliberateError): +class WyppAttributeError(AttributeError, DeliberateError, WyppError): pass diff --git a/python/src/instrument.py b/python/src/instrument.py new file mode 100644 index 00000000..97b455fb --- /dev/null +++ b/python/src/instrument.py @@ -0,0 +1,102 @@ +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 * + +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'}") + methodConfig: ast.expr = parseExp("{'kind': 'method'}") + +def transformStmt(stmt: ast.stmt, insideClass: bool) -> ast.stmt: + # FIXME: static methods + cfg = Configs.methodConfig if insideClass else Configs.funConfig + wrapExp = ast.Call(ast.Name(id='wrapTypechecked', ctx=ast.Load()), [cfg], []) + match stmt: + case ast.FunctionDef(name, args, body, decorators, returns, tyComment, tyParams): + return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) + case ast.ClassDef(name, bases, keywords, body, decorator_list, type_params): + newBody = [transformStmt(s, insideClass=True) for s in body] + return ast.ClassDef(name, bases, keywords, newBody, decorator_list, type_params) + case _: + return stmt + +def transformModule(m: ast.Module | ast.Expression | ast.Interactive) -> ast.Module | ast.Expression | ast.Interactive: + match m: + case ast.Module(body, type_ignores): + newStmts = [transformStmt(stmt, insideClass=False) for stmt in body] + 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") + tree = transformModule(tree) + 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): + self._origFinder = finder + self.modDir = os.path.realpath(modDir) + '/' + + 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) + isLocalModule = origin.startswith(self.modDir) + if spec and spec.loader and isLocalModule: + spec.loader = InstrumentingLoader(spec.loader.name, spec.loader.path) + return spec + +def setupFinder(modDir: str): + 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") + sys.meta_path.insert(0, InstrumentingFinder(finder, modDir)) diff --git a/python/src/myLogging.py b/python/src/myLogging.py new file mode 100644 index 00000000..30e16cf2 --- /dev/null +++ b/python/src/myLogging.py @@ -0,0 +1,20 @@ +import utils +import sys + +VERBOSE = False # set via commandline +DEBUG = utils.getEnv("WYPP_DEBUG", bool, False) + +def enableVerbose(): + global VERBOSE + VERBOSE = 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)) diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py index c5b5f5ef..d7bdd59f 100644 --- a/python/src/myTypeguard.py +++ b/python/src/myTypeguard.py @@ -1,8 +1,8 @@ # Wrapper module for typeguard. Do not import typeguard directly but always via myTypeguard from typing import Any -import typeguard.src.typeguard as typeguard - +# We externally adjust the PYTHONPATH so that the typeguard module can be resolved +import typeguard # type: ignore def matchesTy(a: Any, ty: Any) -> bool: try: @@ -12,5 +12,5 @@ def matchesTy(a: Any, ty: Any) -> bool: return False def renderTy(t: Any) -> str: - return typeguard._utils.qualified_name(t) + return typeguard._utils.get_type_name(t) diff --git a/python/src/runner.py b/python/src/runner.py index 8e2f514e..18a454b2 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -13,31 +13,23 @@ from modulefinder import ModuleFinder from pathlib import Path import subprocess +from myLogging import * +import errors __wypp_runYourProgram = 1 -def die(ecode=1): +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 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'] @@ -46,13 +38,6 @@ def enableVerbose(): 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' @@ -260,7 +245,7 @@ def prepareLib(onlyCheckRunnable, enableTypeChecking): verbose('Attempting to import ' + mod) wypp = importlib.import_module(mod) libDefs = Lib(wypp, True) - verbose('Successfully imported module ' + mod + ' from file ' + wypp.__file__) + verbose(f'Successfully imported module {mod} from file {wypp.__file__}') libDefs.initModule(enableChecks=not onlyCheckRunnable, enableTypeChecking=enableTypeChecking, quiet=onlyCheckRunnable) @@ -429,9 +414,9 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): if frameList and getattr(val, '__wrapped__', False): # We remove the stack frame of the wrapper frameList = frameList[:-1] - isWyppError = isinstance(val, untypy.error.UntypyError) + isWyppError = isinstance(val, errors.WyppError) isBug = not isWyppError and not isinstance(val, SyntaxError) and \ - not isinstance(val, untypy.error.DeliberateError) and frameList \ + not isinstance(val, errors.DeliberateError) and frameList \ and isWyppFrame(frameList[-1]) stackSummary = limitTraceback(frameList, isBug) header = False @@ -440,7 +425,7 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): file.write('Traceback (most recent call last):\n') header = True file.write(x) - if isinstance(val, untypy.error.UntypyError): + if isWyppError: name = 'Wypp' + val.simpleName() file.write(name) s = str(val) diff --git a/python/src/runner2.py b/python/src/runner2.py deleted file mode 100644 index d97e6602..00000000 --- a/python/src/runner2.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -import sys -import runner as r -import importlib -import runpy -LIB_DIR = os.path.dirname(__file__) -TYPEGUARD_DIR = os.path.join(LIB_DIR, "..", "deps", "typeguard", "src") -sys.path.insert(0, TYPEGUARD_DIR) - -import typeguard - -def main(globals, argList=None): - (args, restArgs) = r.parseCmdlineArgs(argList) - typeguard.config.collection_check_strategy = typeguard.CollectionCheckStrategy.ALL_ITEMS - with typeguard.install_import_hook(): - typeguard.config.debug_instrumentation = True - fileToRun = args.file - if args.changeDir: - os.chdir(os.path.dirname(fileToRun)) - fileToRun = os.path.basename(fileToRun) - modDir = os.path.dirname(fileToRun) - sys.path.insert(0, modDir) - modName = os.path.basename(os.path.splitext(fileToRun)[0]) - print('here') - # runpy.run_path(fileToRun, init_globals=globals, run_name=modName) - # __import__(modName) - importlib.import_module(modName) - # import iterator diff --git a/python/src/runner3.py b/python/src/runner3.py index 92e8cec4..45938675 100644 --- a/python/src/runner3.py +++ b/python/src/runner3.py @@ -1,183 +1,12 @@ from __future__ import annotations import sys -import importlib.abc -import types -from collections.abc import Callable, Iterable, Sequence -from importlib.machinery import ModuleSpec, SourceFileLoader -from importlib.util import decode_source -import ast -from os import PathLike -import io -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 -from typing import TypeVar, Any -import inspect -import typing import os -from dataclasses import dataclass - -from myTypeguard import matchesTy, renderTy - -def printVars(what: str, *l): - s = what + ": " + ', '.join([str(x) for x in l]) - print(s) - -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 isEmptyAnnotation(t: Any) -> bool: - return t is inspect.Signature.empty or t is inspect.Parameter.empty - -def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, cfg: CheckCfg) -> None: - params = list(sig.parameters) - if len(params) != len(args): - raise TypeError(f"Expected {len(params)} arguments, got {len(args)}") - for i in range(len(args)): - name = params[i] - t = sig.parameters[name].annotation - if isEmptyAnnotation(t): - if i == 0 and cfg.kind == 'method': - if name != 'self': - raise TypeError(f'Name of first parameter of method {name} must be self not {name}') - else: - raise TypeError(f'Missing type for parameter {name}') - else: - a = args[i] - if not matchesTy(a, t): - raise TypeError(f'Expected argument of type {renderTy(t)} for parameter {name}, got {renderTy(type(a))}: {a}') - -def checkReturn(sig: inspect.Signature, result: Any) -> None: - t = sig.return_annotation - if isEmptyAnnotation(t): - t = None - if not matchesTy(result, t): - raise TypeError(f"Expected return type {renderTy(t)}, got {renderTy(type(result))}: {result}") - -FunKind = typing.Literal['function', 'method', 'staticmethod'] -@dataclass -class CheckCfg: - kind: FunKind - @staticmethod - def fromDict(d: dict) -> CheckCfg: - return CheckCfg(kind=d['kind']) - -def wrap(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: - checkCfg = CheckCfg.fromDict(cfg) - def _wrap(f: Callable[P, T]) -> Callable[P, T]: - sig = inspect.signature(f) - def wrapped(*args, **kwargs) -> T: - checkArguments(sig, args, kwargs, checkCfg) - result = _call_with_frames_removed(f, *args, **kwargs) - checkReturn(sig, result) - return result - return wrapped - return _wrap - -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'}") - methodConfig: ast.expr = parseExp("{'kind': 'method'}") - -def transformStmt(stmt: ast.stmt, insideClass: bool) -> ast.stmt: - # FIXME: static methods - cfg = Configs.methodConfig if insideClass else Configs.funConfig - wrapExp = ast.Call(ast.Name(id='wrap', ctx=ast.Load()), [cfg], []) - match stmt: - case ast.FunctionDef(name, args, body, decorators, returns, tyComment, tyParams): - return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) - case ast.ClassDef(name, bases, keywords, body, decorator_list, type_params): - newBody = [transformStmt(s, insideClass=True) for s in body] - return ast.ClassDef(name, bases, keywords, newBody, decorator_list, type_params) - case _: - return stmt - -def transformModule(m: ast.Module | ast.Expression | ast.Interactive) -> ast.Module | ast.Expression | ast.Interactive: - match m: - case ast.Module(body, type_ignores): - newStmts = [transformStmt(stmt, insideClass=False) for stmt in body] - 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 = _call_with_frames_removed(ast.parse, source, path, "exec") - tree = transformModule(tree) - ast.fix_missing_locations(tree) - - print( - f"Source code of {path!r} after instrumentation:\n" - "----------------------------------------------", - file=sys.stderr, - ) - print(ast.unparse(tree), file=sys.stderr) - print("----------------------------------------------", file=sys.stderr) - - code = _call_with_frames_removed(compile, tree, path, "exec", 0, dont_inherit=True) - return code - -class InstrumentingFinder(importlib.abc.MetaPathFinder): - def __init__(self, finder, modDir): - self._origFinder = finder - self.modDir = os.path.realpath(modDir) + '/' - - 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) - origin = os.path.realpath(spec.origin) - isLocalModule = origin.startswith(self.modDir) - if spec and spec.loader and isLocalModule: - spec.loader = InstrumentingLoader(spec.loader.name, spec.loader.path) - return spec - -def setupFinder(modDir: str): - 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") - sys.meta_path.insert(0, InstrumentingFinder(finder, modDir)) - +import instrument +import typecheck import runner as r import os import runpy + def main(globals, argList=None): print('runner3') (args, restArgs) = r.parseCmdlineArgs(argList) @@ -186,11 +15,10 @@ def main(globals, argList=None): os.chdir(os.path.dirname(fileToRun)) fileToRun = os.path.basename(fileToRun) modDir = os.path.dirname(fileToRun) - setupFinder(modDir) + instrument.setupFinder(modDir) sys.path.insert(0, modDir) modName = os.path.basename(os.path.splitext(fileToRun)[0]) - globals['wrap'] = wrap + globals['wrapTypecheck'] = typecheck.wrapTypecheck + print(f'Running module {modName}, file={fileToRun}') + sys.dont_write_bytecode = True runpy.run_module(modName, init_globals=globals, run_name=modName) - # __import__(modName) - # importlib.import_module(modName) - # import iterator diff --git a/python/src/typecheck.py b/python/src/typecheck.py new file mode 100644 index 00000000..def6dc2b --- /dev/null +++ b/python/src/typecheck.py @@ -0,0 +1,65 @@ +from __future__ import annotations +from collections.abc import Callable +from typing import ParamSpec +from typing import TypeVar, Any +import inspect +import typing +from dataclasses import dataclass +import utils +from myTypeguard import matchesTy, renderTy + +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 checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, cfg: CheckCfg) -> None: + params = list(sig.parameters) + if len(params) != len(args): + raise TypeError(f"Expected {len(params)} arguments, got {len(args)}") + for i in range(len(args)): + name = params[i] + t = sig.parameters[name].annotation + if isEmptyAnnotation(t): + if i == 0 and cfg.kind == 'method': + if name != 'self': + raise TypeError(f'Name of first parameter of method {name} must be self not {name}') + else: + raise TypeError(f'Missing type for parameter {name}') + else: + a = args[i] + if not matchesTy(a, t): + raise TypeError(f'Expected argument of type {renderTy(t)} for parameter {name}, got {renderTy(type(a))}: {a}') + +def checkReturn(sig: inspect.Signature, result: Any) -> None: + t = sig.return_annotation + if isEmptyAnnotation(t): + t = None + if not matchesTy(result, t): + print(t) + raise TypeError(f"Expected return value of type {renderTy(t)}, got {renderTy(type(result))}: {result}") + +FunKind = typing.Literal['function', 'method', 'staticmethod'] +@dataclass +class CheckCfg: + kind: FunKind + @staticmethod + def fromDict(d: dict) -> CheckCfg: + return CheckCfg(kind=d['kind']) + +P = ParamSpec("P") +T = TypeVar("T") + +def wrapTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: + checkCfg = CheckCfg.fromDict(cfg) + def _wrap(f: Callable[P, T]) -> Callable[P, T]: + sig = inspect.signature(f) + def wrapped(*args, **kwargs) -> T: + checkArguments(sig, args, kwargs, checkCfg) + result = utils._call_with_frames_removed(f, *args, **kwargs) + checkReturn(sig, result) + return result + return wrapped + return _wrap diff --git a/python/src/utils.py b/python/src/utils.py new file mode 100644 index 00000000..16e1288f --- /dev/null +++ b/python/src/utils.py @@ -0,0 +1,21 @@ +from typing import * +import os + +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 getEnv(name, conv, default): + s = os.getenv(name) + if s is None: + return default + try: + return conv(s) + except: + return default diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index 43185343..0261006f 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -1,10 +1,10 @@ # FIXME: make exceptions nicer -import untypy import typing import dataclasses import inspect import myTypeguard import errors +import typecheck _DEBUG = False def _debug(s): @@ -83,8 +83,9 @@ def formatArg(x): # 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) +# 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 # This patch is needed to provide better error messages if a student passes type arguments # with paranthesis instead of square brackets @@ -107,7 +108,7 @@ def _patchDataClass(cls, mutable): cls.__kind = 'record' cls.__init__.__annotations__ = _collectDataClassAttributes(cls) cls.__init__.__original = cls # mark class as source of annotation - cls.__init__ = untypy.typechecked(cls.__init__) + cls.__init__ = typecheck.wrapTypecheck(cls.__init__) if mutable: # prevent new fields being added @@ -118,12 +119,12 @@ def _patchDataClass(cls, mutable): # 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) + ty = typing.get_type_hints(cls, include_extras=True)[name] + def check(v): + if not myTypeguard.matchesTy(v, ty): + raise TypeError(f'Expected argument of type {myTypeguard.renderTy(ty)} ' \ + f'for attribute {name}, got {myTypeguard.renderTy(type(v))}: {v}') + checker[name] = check oldSetattr = cls.__setattr__ def _setattr(obj, k, v): @@ -258,8 +259,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 @@ -353,7 +357,7 @@ def deepEq(v1, v2, **flags): return False # v1 == v2 already checked return False -class TodoError(Exception, untypy.error.DeliberateError): +class TodoError(Exception, errors.DeliberateError): pass def todo(msg=None): @@ -361,7 +365,7 @@ def todo(msg=None): msg = 'TODO' raise TodoError(msg) -class ImpossibleError(Exception, untypy.error.DeliberateError): +class ImpossibleError(Exception, errors.DeliberateError): pass def impossible(msg=None): diff --git a/python/test-data-2.0/iterator.err b/python/test-data-2.0/iterator.err new file mode 100644 index 00000000..b414d2ea --- /dev/null +++ b/python/test-data-2.0/iterator.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + ... + +WyppTypeError: got value of wrong type +Given: 1 +Expected: value of type Iterator[int] + +Location: test-data-2.0/iterator.err, line 4 + +Context: + +3| def my_generator() -> <>: +4| return <> diff --git a/python/test-data/testLiteralInstanceOf.out b/python/test-data/testLiteralInstanceOf.out index 483b67ec..34be1684 100644 --- a/python/test-data/testLiteralInstanceOf.out +++ b/python/test-data/testLiteralInstanceOf.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/test-data/testLiteralInstanceOf.py index 9f653f11..32b97afb 100644 --- a/python/test-data/testLiteralInstanceOf.py +++ b/python/test-data/testLiteralInstanceOf.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/trash/untypy/.gitignore b/python/trash/untypy/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/python/trash/untypy/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/python/trash/untypy/LICENSE b/python/trash/untypy/LICENSE new file mode 100644 index 00000000..45d7ebab --- /dev/null +++ b/python/trash/untypy/LICENSE @@ -0,0 +1,21 @@ +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/trash/untypy/README.rst b/python/trash/untypy/README.rst new file mode 100644 index 00000000..741895cf --- /dev/null +++ b/python/trash/untypy/README.rst @@ -0,0 +1,43 @@ +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/trash/untypy/examples/ex01.py b/python/trash/untypy/examples/ex01.py new file mode 100644 index 00000000..ec949b9e --- /dev/null +++ b/python/trash/untypy/examples/ex01.py @@ -0,0 +1,18 @@ +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/trash/untypy/examples/ex02.py b/python/trash/untypy/examples/ex02.py new file mode 100644 index 00000000..db5fd2ea --- /dev/null +++ b/python/trash/untypy/examples/ex02.py @@ -0,0 +1,26 @@ +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/trash/untypy/examples/ex03.py b/python/trash/untypy/examples/ex03.py new file mode 100644 index 00000000..291a12f4 --- /dev/null +++ b/python/trash/untypy/examples/ex03.py @@ -0,0 +1,14 @@ +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/trash/untypy/examples/ex04.py b/python/trash/untypy/examples/ex04.py new file mode 100644 index 00000000..063eaf86 --- /dev/null +++ b/python/trash/untypy/examples/ex04.py @@ -0,0 +1,17 @@ +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/trash/untypy/examples/ex05.py b/python/trash/untypy/examples/ex05.py new file mode 100644 index 00000000..4559efed --- /dev/null +++ b/python/trash/untypy/examples/ex05.py @@ -0,0 +1,30 @@ +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/trash/untypy/examples/ex06.py b/python/trash/untypy/examples/ex06.py new file mode 100644 index 00000000..7f338277 --- /dev/null +++ b/python/trash/untypy/examples/ex06.py @@ -0,0 +1,15 @@ +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/trash/untypy/examples/ex09.py b/python/trash/untypy/examples/ex09.py new file mode 100644 index 00000000..e5afe101 --- /dev/null +++ b/python/trash/untypy/examples/ex09.py @@ -0,0 +1,11 @@ +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/trash/untypy/examples/ex10.py b/python/trash/untypy/examples/ex10.py new file mode 100644 index 00000000..c16affd6 --- /dev/null +++ b/python/trash/untypy/examples/ex10.py @@ -0,0 +1,17 @@ +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/trash/untypy/examples/mylogfile b/python/trash/untypy/examples/mylogfile new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/requirements.txt b/python/trash/untypy/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/runtests.sh b/python/trash/untypy/runtests.sh new file mode 100755 index 00000000..ac2b2bb3 --- /dev/null +++ b/python/trash/untypy/runtests.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +python3 -m unittest discover "$@" + diff --git a/python/trash/untypy/setup.py b/python/trash/untypy/setup.py new file mode 100644 index 00000000..c4bb0ea0 --- /dev/null +++ b/python/trash/untypy/setup.py @@ -0,0 +1,12 @@ +#!/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/trash/untypy/test-requirements.txt b/python/trash/untypy/test-requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/test/__init__.py b/python/trash/untypy/test/__init__.py new file mode 100644 index 00000000..12cfdd65 --- /dev/null +++ b/python/trash/untypy/test/__init__.py @@ -0,0 +1,3 @@ +import untypy + +untypy.GlobalConfig = untypy.GlobalConfig._replace(checkedprefixes=["test"]) diff --git a/python/trash/untypy/test/impl/__init__.py b/python/trash/untypy/test/impl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/test/impl/test_any.py b/python/trash/untypy/test/impl/test_any.py new file mode 100644 index 00000000..98698a80 --- /dev/null +++ b/python/trash/untypy/test/impl/test_any.py @@ -0,0 +1,15 @@ +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/trash/untypy/test/impl/test_callable.py b/python/trash/untypy/test/impl/test_callable.py new file mode 100644 index 00000000..bbe772c9 --- /dev/null +++ b/python/trash/untypy/test/impl/test_callable.py @@ -0,0 +1,78 @@ +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/trash/untypy/test/impl/test_dict.py b/python/trash/untypy/test/impl/test_dict.py new file mode 100644 index 00000000..42851819 --- /dev/null +++ b/python/trash/untypy/test/impl/test_dict.py @@ -0,0 +1,87 @@ +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/trash/untypy/test/impl/test_forwardRef.py b/python/trash/untypy/test/impl/test_forwardRef.py new file mode 100644 index 00000000..3db02492 --- /dev/null +++ b/python/trash/untypy/test/impl/test_forwardRef.py @@ -0,0 +1,57 @@ +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/trash/untypy/test/impl/test_generator.py b/python/trash/untypy/test/impl/test_generator.py new file mode 100644 index 00000000..11585561 --- /dev/null +++ b/python/trash/untypy/test/impl/test_generator.py @@ -0,0 +1,167 @@ +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/trash/untypy/test/impl/test_interface_dict.py b/python/trash/untypy/test/impl/test_interface_dict.py new file mode 100644 index 00000000..540b3af4 --- /dev/null +++ b/python/trash/untypy/test/impl/test_interface_dict.py @@ -0,0 +1,235 @@ +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/trash/untypy/test/impl/test_interface_set.py b/python/trash/untypy/test/impl/test_interface_set.py new file mode 100644 index 00000000..74ecb9b5 --- /dev/null +++ b/python/trash/untypy/test/impl/test_interface_set.py @@ -0,0 +1,90 @@ +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/trash/untypy/test/impl/test_list.py b/python/trash/untypy/test/impl/test_list.py new file mode 100644 index 00000000..ec757edc --- /dev/null +++ b/python/trash/untypy/test/impl/test_list.py @@ -0,0 +1,348 @@ +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/trash/untypy/test/impl/test_literal.py b/python/trash/untypy/test/impl/test_literal.py new file mode 100644 index 00000000..ded683e9 --- /dev/null +++ b/python/trash/untypy/test/impl/test_literal.py @@ -0,0 +1,29 @@ +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/trash/untypy/test/impl/test_none.py b/python/trash/untypy/test/impl/test_none.py new file mode 100644 index 00000000..198bbbc5 --- /dev/null +++ b/python/trash/untypy/test/impl/test_none.py @@ -0,0 +1,35 @@ +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/trash/untypy/test/impl/test_optional.py b/python/trash/untypy/test/impl/test_optional.py new file mode 100644 index 00000000..f64b0e71 --- /dev/null +++ b/python/trash/untypy/test/impl/test_optional.py @@ -0,0 +1,49 @@ +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/trash/untypy/test/impl/test_protocol.py b/python/trash/untypy/test/impl/test_protocol.py new file mode 100644 index 00000000..2afb8a79 --- /dev/null +++ b/python/trash/untypy/test/impl/test_protocol.py @@ -0,0 +1,280 @@ +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/trash/untypy/test/impl/test_set.py b/python/trash/untypy/test/impl/test_set.py new file mode 100644 index 00000000..ae2562d9 --- /dev/null +++ b/python/trash/untypy/test/impl/test_set.py @@ -0,0 +1,171 @@ +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/trash/untypy/test/impl/test_simple.py b/python/trash/untypy/test/impl/test_simple.py new file mode 100644 index 00000000..6e70e37d --- /dev/null +++ b/python/trash/untypy/test/impl/test_simple.py @@ -0,0 +1,160 @@ +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/trash/untypy/test/impl/test_str.py b/python/trash/untypy/test/impl/test_str.py new file mode 100644 index 00000000..a3b28d79 --- /dev/null +++ b/python/trash/untypy/test/impl/test_str.py @@ -0,0 +1,96 @@ +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/trash/untypy/test/impl/test_tuple.py b/python/trash/untypy/test/impl/test_tuple.py new file mode 100644 index 00000000..a7edb3f9 --- /dev/null +++ b/python/trash/untypy/test/impl/test_tuple.py @@ -0,0 +1,156 @@ +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/trash/untypy/test/impl/test_union.py b/python/trash/untypy/test/impl/test_union.py new file mode 100644 index 00000000..18e68e70 --- /dev/null +++ b/python/trash/untypy/test/impl/test_union.py @@ -0,0 +1,71 @@ +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/trash/untypy/test/patching_dummy/__init__.py b/python/trash/untypy/test/patching_dummy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/test/patching_dummy/a/__init__.py b/python/trash/untypy/test/patching_dummy/a/__init__.py new file mode 100644 index 00000000..369e1ccc --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/a/__init__.py @@ -0,0 +1,5 @@ +from .a_sub import fn_two + + +def fn_one(x: int) -> None: + pass diff --git a/python/trash/untypy/test/patching_dummy/a/a_sub.py b/python/trash/untypy/test/patching_dummy/a/a_sub.py new file mode 100644 index 00000000..2745d71a --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/a/a_sub.py @@ -0,0 +1,2 @@ +def fn_two(x: int) -> None: + pass diff --git a/python/trash/untypy/test/patching_dummy/argument_types.py b/python/trash/untypy/test/patching_dummy/argument_types.py new file mode 100644 index 00000000..da2f8106 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/argument_types.py @@ -0,0 +1,9 @@ +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/trash/untypy/test/patching_dummy/async_gen.py b/python/trash/untypy/test/patching_dummy/async_gen.py new file mode 100644 index 00000000..752ce8f0 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/async_gen.py @@ -0,0 +1,16 @@ +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/trash/untypy/test/patching_dummy/b/__init__.py b/python/trash/untypy/test/patching_dummy/b/__init__.py new file mode 100644 index 00000000..26da4915 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/b/__init__.py @@ -0,0 +1,5 @@ +from .b_sub import fn_two + + +def fn_one(x: int) -> None: + pass diff --git a/python/trash/untypy/test/patching_dummy/b/b_sub.py b/python/trash/untypy/test/patching_dummy/b/b_sub.py new file mode 100644 index 00000000..2745d71a --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/b/b_sub.py @@ -0,0 +1,2 @@ +def fn_two(x: int) -> None: + pass diff --git a/python/trash/untypy/test/patching_dummy/c.py b/python/trash/untypy/test/patching_dummy/c.py new file mode 100644 index 00000000..80f56399 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/c.py @@ -0,0 +1,5 @@ +def fn_one(x: int) -> None: + pass + + +untypy.enable() diff --git a/python/trash/untypy/test/patching_dummy/patching_classes.py b/python/trash/untypy/test/patching_dummy/patching_classes.py new file mode 100644 index 00000000..a088ba5f --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/patching_classes.py @@ -0,0 +1,6 @@ +class A: + def __init__(self, y: int): + self.y = y + + def add(self, x: int) -> int: + return x + self.y diff --git a/python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py b/python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py new file mode 100644 index 00000000..5ba12547 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py @@ -0,0 +1,7 @@ +def fun(x: int) -> str: + return str(x) + + +class SomeClass: + def meth(self, x: int) -> str: + return str(x) diff --git a/python/trash/untypy/test/patching_dummy/unpatched.py b/python/trash/untypy/test/patching_dummy/unpatched.py new file mode 100644 index 00000000..582d5317 --- /dev/null +++ b/python/trash/untypy/test/patching_dummy/unpatched.py @@ -0,0 +1,2 @@ +def fn_one(x: int) -> None: + pass diff --git a/python/trash/untypy/test/test_patching.py b/python/trash/untypy/test/test_patching.py new file mode 100644 index 00000000..b1136e2d --- /dev/null +++ b/python/trash/untypy/test/test_patching.py @@ -0,0 +1,24 @@ +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/trash/untypy/test/test_standalone_checker.py b/python/trash/untypy/test/test_standalone_checker.py new file mode 100644 index 00000000..66190e8c --- /dev/null +++ b/python/trash/untypy/test/test_standalone_checker.py @@ -0,0 +1,21 @@ +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/trash/untypy/test/util.py b/python/trash/untypy/test/util.py new file mode 100644 index 00000000..6e20f8f2 --- /dev/null +++ b/python/trash/untypy/test/util.py @@ -0,0 +1,35 @@ +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/trash/untypy/test/util_test/__init__.py b/python/trash/untypy/test/util_test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/test/util_test/test_return_traces.py b/python/trash/untypy/test/util_test/test_return_traces.py new file mode 100644 index 00000000..5c9e3337 --- /dev/null +++ b/python/trash/untypy/test/util_test/test_return_traces.py @@ -0,0 +1,36 @@ +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/trash/untypy/test/util_test/test_wrapper.py b/python/trash/untypy/test/util_test/test_wrapper.py new file mode 100644 index 00000000..9d3d921d --- /dev/null +++ b/python/trash/untypy/test/util_test/test_wrapper.py @@ -0,0 +1,170 @@ +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/trash/untypy/test/util_test/untypy_test_case.py b/python/trash/untypy/test/util_test/untypy_test_case.py new file mode 100644 index 00000000..52228f37 --- /dev/null +++ b/python/trash/untypy/test/util_test/untypy_test_case.py @@ -0,0 +1,30 @@ +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/trash/untypy/untypy/__init__.py b/python/trash/untypy/untypy/__init__.py new file mode 100644 index 00000000..5f451216 --- /dev/null +++ b/python/trash/untypy/untypy/__init__.py @@ -0,0 +1,187 @@ +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/trash/untypy/untypy/error.py b/python/trash/untypy/untypy/error.py new file mode 100644 index 00000000..0ffeda72 --- /dev/null +++ b/python/trash/untypy/untypy/error.py @@ -0,0 +1,384 @@ +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/trash/untypy/untypy/impl/__init__.py b/python/trash/untypy/untypy/impl/__init__.py new file mode 100644 index 00000000..d147d692 --- /dev/null +++ b/python/trash/untypy/untypy/impl/__init__.py @@ -0,0 +1,108 @@ +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/trash/untypy/untypy/impl/alias.py b/python/trash/untypy/untypy/impl/alias.py new file mode 100644 index 00000000..559a4c85 --- /dev/null +++ b/python/trash/untypy/untypy/impl/alias.py @@ -0,0 +1,12 @@ +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/trash/untypy/untypy/impl/annotated.py b/python/trash/untypy/untypy/impl/annotated.py new file mode 100644 index 00000000..f9119228 --- /dev/null +++ b/python/trash/untypy/untypy/impl/annotated.py @@ -0,0 +1,137 @@ +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/trash/untypy/untypy/impl/any.py b/python/trash/untypy/untypy/impl/any.py new file mode 100644 index 00000000..62671c8f --- /dev/null +++ b/python/trash/untypy/untypy/impl/any.py @@ -0,0 +1,34 @@ +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/trash/untypy/untypy/impl/callable.py b/python/trash/untypy/untypy/impl/callable.py new file mode 100644 index 00000000..2d092e12 --- /dev/null +++ b/python/trash/untypy/untypy/impl/callable.py @@ -0,0 +1,277 @@ +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/trash/untypy/untypy/impl/choice.py b/python/trash/untypy/untypy/impl/choice.py new file mode 100644 index 00000000..fefd5776 --- /dev/null +++ b/python/trash/untypy/untypy/impl/choice.py @@ -0,0 +1,66 @@ + +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/trash/untypy/untypy/impl/dummy_delayed.py b/python/trash/untypy/untypy/impl/dummy_delayed.py new file mode 100644 index 00000000..81b07704 --- /dev/null +++ b/python/trash/untypy/untypy/impl/dummy_delayed.py @@ -0,0 +1,43 @@ +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/trash/untypy/untypy/impl/generator.py b/python/trash/untypy/untypy/impl/generator.py new file mode 100644 index 00000000..6551bdb3 --- /dev/null +++ b/python/trash/untypy/untypy/impl/generator.py @@ -0,0 +1,128 @@ +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/trash/untypy/untypy/impl/generic.py b/python/trash/untypy/untypy/impl/generic.py new file mode 100644 index 00000000..0f0e65b5 --- /dev/null +++ b/python/trash/untypy/untypy/impl/generic.py @@ -0,0 +1,98 @@ +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/trash/untypy/untypy/impl/interface.py b/python/trash/untypy/untypy/impl/interface.py new file mode 100644 index 00000000..9ecb5957 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interface.py @@ -0,0 +1,87 @@ +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/trash/untypy/untypy/impl/interfaces/__init__.py b/python/trash/untypy/untypy/impl/interfaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/trash/untypy/untypy/impl/interfaces/dict.py b/python/trash/untypy/untypy/impl/interfaces/dict.py new file mode 100644 index 00000000..fdb76902 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/dict.py @@ -0,0 +1,68 @@ +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/trash/untypy/untypy/impl/interfaces/iterable.py b/python/trash/untypy/untypy/impl/interfaces/iterable.py new file mode 100644 index 00000000..b52c7db1 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/iterable.py @@ -0,0 +1,25 @@ +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/trash/untypy/untypy/impl/interfaces/list.py b/python/trash/untypy/untypy/impl/interfaces/list.py new file mode 100644 index 00000000..d624d8f3 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/list.py @@ -0,0 +1,56 @@ +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/trash/untypy/untypy/impl/interfaces/sequence.py b/python/trash/untypy/untypy/impl/interfaces/sequence.py new file mode 100644 index 00000000..08d58371 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/sequence.py @@ -0,0 +1,31 @@ +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/trash/untypy/untypy/impl/interfaces/set.py b/python/trash/untypy/untypy/impl/interfaces/set.py new file mode 100644 index 00000000..145cb9b1 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/set.py @@ -0,0 +1,53 @@ +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/trash/untypy/untypy/impl/interfaces/util.py b/python/trash/untypy/untypy/impl/interfaces/util.py new file mode 100644 index 00000000..42e5f170 --- /dev/null +++ b/python/trash/untypy/untypy/impl/interfaces/util.py @@ -0,0 +1,6 @@ +def overwrite(typ): + def inner(fn): + setattr(fn, '__overwrite', typ) + return fn + + return inner diff --git a/python/trash/untypy/untypy/impl/literal.py b/python/trash/untypy/untypy/impl/literal.py new file mode 100644 index 00000000..03de3e53 --- /dev/null +++ b/python/trash/untypy/untypy/impl/literal.py @@ -0,0 +1,36 @@ +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/trash/untypy/untypy/impl/none.py b/python/trash/untypy/untypy/impl/none.py new file mode 100644 index 00000000..31180286 --- /dev/null +++ b/python/trash/untypy/untypy/impl/none.py @@ -0,0 +1,27 @@ +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/trash/untypy/untypy/impl/optional.py b/python/trash/untypy/untypy/impl/optional.py new file mode 100644 index 00000000..b1577c6b --- /dev/null +++ b/python/trash/untypy/untypy/impl/optional.py @@ -0,0 +1,44 @@ +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/trash/untypy/untypy/impl/protocol.py b/python/trash/untypy/untypy/impl/protocol.py new file mode 100644 index 00000000..e98d0385 --- /dev/null +++ b/python/trash/untypy/untypy/impl/protocol.py @@ -0,0 +1,524 @@ +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/trash/untypy/untypy/impl/simple.py b/python/trash/untypy/untypy/impl/simple.py new file mode 100644 index 00000000..4cb39da7 --- /dev/null +++ b/python/trash/untypy/untypy/impl/simple.py @@ -0,0 +1,68 @@ +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/trash/untypy/untypy/impl/string_forward_refs.py b/python/trash/untypy/untypy/impl/string_forward_refs.py new file mode 100644 index 00000000..85fa8a21 --- /dev/null +++ b/python/trash/untypy/untypy/impl/string_forward_refs.py @@ -0,0 +1,19 @@ +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/trash/untypy/untypy/impl/tuple.py b/python/trash/untypy/untypy/impl/tuple.py new file mode 100644 index 00000000..dd5836bc --- /dev/null +++ b/python/trash/untypy/untypy/impl/tuple.py @@ -0,0 +1,103 @@ +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/trash/untypy/untypy/impl/union.py b/python/trash/untypy/untypy/impl/union.py new file mode 100644 index 00000000..b6c973e1 --- /dev/null +++ b/python/trash/untypy/untypy/impl/union.py @@ -0,0 +1,80 @@ +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/trash/untypy/untypy/interfaces.py b/python/trash/untypy/untypy/interfaces.py new file mode 100644 index 00000000..d6ad5e17 --- /dev/null +++ b/python/trash/untypy/untypy/interfaces.py @@ -0,0 +1,105 @@ +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/trash/untypy/untypy/patching/__init__.py b/python/trash/untypy/untypy/patching/__init__.py new file mode 100644 index 00000000..35a1e6c8 --- /dev/null +++ b/python/trash/untypy/untypy/patching/__init__.py @@ -0,0 +1,59 @@ +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/trash/untypy/untypy/patching/ast_transformer.py b/python/trash/untypy/untypy/patching/ast_transformer.py new file mode 100644 index 00000000..f48ee49b --- /dev/null +++ b/python/trash/untypy/untypy/patching/ast_transformer.py @@ -0,0 +1,165 @@ +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/trash/untypy/untypy/patching/import_hook.py b/python/trash/untypy/untypy/patching/import_hook.py new file mode 100644 index 00000000..ac44cb02 --- /dev/null +++ b/python/trash/untypy/untypy/patching/import_hook.py @@ -0,0 +1,65 @@ +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/trash/untypy/untypy/patching/standalone_checker.py b/python/trash/untypy/untypy/patching/standalone_checker.py new file mode 100644 index 00000000..e31428c2 --- /dev/null +++ b/python/trash/untypy/untypy/patching/standalone_checker.py @@ -0,0 +1,59 @@ +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/trash/untypy/untypy/util/__init__.py b/python/trash/untypy/untypy/util/__init__.py new file mode 100644 index 00000000..b9e3a845 --- /dev/null +++ b/python/trash/untypy/untypy/util/__init__.py @@ -0,0 +1,260 @@ +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/trash/untypy/untypy/util/condition.py b/python/trash/untypy/untypy/util/condition.py new file mode 100644 index 00000000..7a053740 --- /dev/null +++ b/python/trash/untypy/untypy/util/condition.py @@ -0,0 +1,130 @@ +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/trash/untypy/untypy/util/debug.py b/python/trash/untypy/untypy/util/debug.py new file mode 100644 index 00000000..9f2293be --- /dev/null +++ b/python/trash/untypy/untypy/util/debug.py @@ -0,0 +1,24 @@ +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/trash/untypy/untypy/util/display.py b/python/trash/untypy/untypy/util/display.py new file mode 100644 index 00000000..8e73c940 --- /dev/null +++ b/python/trash/untypy/untypy/util/display.py @@ -0,0 +1,44 @@ +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/trash/untypy/untypy/util/return_traces.py b/python/trash/untypy/untypy/util/return_traces.py new file mode 100644 index 00000000..84eca8b0 --- /dev/null +++ b/python/trash/untypy/untypy/util/return_traces.py @@ -0,0 +1,75 @@ +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/trash/untypy/untypy/util/source_utils.py b/python/trash/untypy/untypy/util/source_utils.py new file mode 100644 index 00000000..6847c21a --- /dev/null +++ b/python/trash/untypy/untypy/util/source_utils.py @@ -0,0 +1,36 @@ +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/trash/untypy/untypy/util/tranformer_combinator.py b/python/trash/untypy/untypy/util/tranformer_combinator.py new file mode 100644 index 00000000..c86de398 --- /dev/null +++ b/python/trash/untypy/untypy/util/tranformer_combinator.py @@ -0,0 +1,11 @@ +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/trash/untypy/untypy/util/typedfunction.py b/python/trash/untypy/untypy/util/typedfunction.py new file mode 100644 index 00000000..8c6624a5 --- /dev/null +++ b/python/trash/untypy/untypy/util/typedfunction.py @@ -0,0 +1,218 @@ +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/trash/untypy/untypy/util/typehints.py b/python/trash/untypy/untypy/util/typehints.py new file mode 100644 index 00000000..8f9947c7 --- /dev/null +++ b/python/trash/untypy/untypy/util/typehints.py @@ -0,0 +1,133 @@ +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/trash/untypy/untypy/util/wrapper.py b/python/trash/untypy/untypy/util/wrapper.py new file mode 100644 index 00000000..f1a5494f --- /dev/null +++ b/python/trash/untypy/untypy/util/wrapper.py @@ -0,0 +1,292 @@ +import typing +import collections +import abc +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) + +_wrapperClasses = {} + +def wrapObj(wrapped, methods, name, extra): + k = wrapped.__class__ + if k in _wrapperClasses: + cls = _wrapperClasses[k] + else: + if isinstance(wrapped, abc.ABC): + class BaseWrapper(WrapperBase, wrapped.__class__, abc.ABC): + def __init__(self, wrapped): + self.__dict__ = wrapped.__dict__ + self.__wrapped__ = wrapped + _wrapperClasses[k] = BaseWrapper + cls = BaseWrapper + else: + class BaseWrapper(WrapperBase, wrapped.__class__): + def __init__(self, wrapped): + self.__dict__ = wrapped.__dict__ + self.__wrapped__ = wrapped + _wrapperClasses[k] = BaseWrapper + cls = BaseWrapper + if name is None: + name = 'ObjectWrapper' + if hasattr(wrapped, '__module__'): + mod = getattr(wrapped, '__module__') + else: + mod = None + return _wrap(wrapped, methods, mod, name, extra, cls) + +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 From 11932fae823c05a4233b7f41379af8bf93f3e2eb Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 7 Jul 2025 18:46:05 +0200 Subject: [PATCH 14/56] remove most occurrences of untypy --- python/src/errors.py | 9 +- python/src/instrument.py | 2 +- python/src/replTester.py | 10 +- python/src/runYourProgram.py | 2 +- python/src/runner.py | 199 +++++++++++++++++---------------- python/src/typecheck.py | 11 +- python/src/utils.py | 9 +- python/src/writeYourProgram.py | 4 +- 8 files changed, 132 insertions(+), 114 deletions(-) diff --git a/python/src/errors.py b/python/src/errors.py index 123cfa3b..fa3a3012 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -1,5 +1,6 @@ from typing import * from dataclasses import dataclass +import abc @dataclass class Pos: @@ -12,17 +13,17 @@ class Location: startPos: Pos endPos: Pos -class WyppError: +class WyppError(abc.ABC): pass # DeliberateError instances are not reported as bugs -class DeliberateError(WyppError): +class DeliberateError(WyppError, abc.ABC): pass -class WyppTypeError(TypeError, DeliberateError, WyppError): +class WyppTypeError(TypeError, DeliberateError): # def __init__(self, expectedTy: Any, givenValue: Any) pass -class WyppAttributeError(AttributeError, DeliberateError, WyppError): +class WyppAttributeError(AttributeError, DeliberateError): pass diff --git a/python/src/instrument.py b/python/src/instrument.py index 97b455fb..7385fb66 100644 --- a/python/src/instrument.py +++ b/python/src/instrument.py @@ -25,7 +25,7 @@ class Configs: def transformStmt(stmt: ast.stmt, insideClass: bool) -> ast.stmt: # FIXME: static methods cfg = Configs.methodConfig if insideClass else Configs.funConfig - wrapExp = ast.Call(ast.Name(id='wrapTypechecked', ctx=ast.Load()), [cfg], []) + wrapExp = ast.Call(ast.Name(id='wrapTypecheck', ctx=ast.Load()), [cfg], []) match stmt: case ast.FunctionDef(name, args, body, decorators, returns, tyComment, tyParams): return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) diff --git a/python/src/replTester.py b/python/src/replTester.py index 426a2e2c..8f68492f 100644 --- a/python/src/replTester.py +++ b/python/src/replTester.py @@ -3,7 +3,7 @@ import os import argparse from dataclasses import dataclass -from runner import runCode, importUntypy, verbose, enableVerbose +from runner import runCode, importTypeguard, verbose, enableVerbose usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m @@ -56,7 +56,7 @@ def parseCmdlineArgs(): libDir = os.path.dirname(__file__) libFile = os.path.join(libDir, 'writeYourProgram.py') defs = globals() -importUntypy() +importTypeguard() # runCode(libFile, defs, []) for lib in opts.libs: @@ -79,9 +79,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(): diff --git a/python/src/runYourProgram.py b/python/src/runYourProgram.py index 515be54c..20f0d7ce 100644 --- a/python/src/runYourProgram.py +++ b/python/src/runYourProgram.py @@ -13,5 +13,5 @@ sys.exit(1) if __name__ == '__main__': - import runner3 as r + import runner as r r.main(globals()) diff --git a/python/src/runner.py b/python/src/runner.py index 18a454b2..9ff420a8 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -9,10 +9,16 @@ import importlib import re import code -import ast from modulefinder import ModuleFinder from pathlib import Path import subprocess +import runpy +import types +from dataclasses import dataclass + +# local imports +import typecheck +import instrument from myLogging import * import errors @@ -34,8 +40,8 @@ def die(ecode: str | int | None = 1): 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' +TYPEGUARD_DIR = os.path.join(LIB_DIR, "..", "deps", "typeguard") +TYPEGUARD_MODULE_NAME = 'typeguard' SITELIB_DIR = os.path.join(LIB_DIR, "..", "site-lib") class InstallMode: @@ -142,14 +148,14 @@ def readVersion(): pass return version -def printWelcomeString(file, version, useUntypy): +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 useUntypy: + if not doTypecheck: tycheck = ', no typechecking' printStderr('=== WELCOME to "Write Your Python Program" ' + '(%sPython %s, %s%s) ===' % (versionStr, pythonVersion, file, tycheck)) @@ -202,7 +208,7 @@ def installLib(mode): 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) + allEq2 = installFromDir(TYPEGUARD_DIR, targetDir, TYPEGUARD_MODULE_NAME) if allEq1 and allEq2: verbose(f'WYPP library in {targetDir} already up to date') if mode == InstallMode.installOnly: @@ -274,8 +280,8 @@ def findImportedModules(path, file): 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__) + if name != '__main__' and (f := mod.__file__): # type: ignore + realp = os.path.realpath(f) good = False for d in realdirs: if realp.startswith(d): @@ -285,51 +291,40 @@ def findImportedModules(path, file): res.append(name) return res +@dataclass class RunSetup: - def __init__(self, sysPath): - self.sysPath = sysPath + 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.sysPath not in sys.path: - sys.path.insert(0, self.sysPath) + if self.pathDir not in sys.path: + sys.path.insert(0, self.pathDir) self.sysPathInserted = True + self.argv = self.args def __exit__(self, exc_type, value, traceback): if self.sysPathInserted: - sys.path.remove(self.sysPath) + sys.path.remove(self.pathDir) self.sysPathInserted = False + self.argv = self.oldArgs -def runCode(fileToRun, globals, args, useUntypy=True, extraDirs=None): +def runCode(fileToRun, globals, args, doTypecheck=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 + modDir = os.path.dirname(fileToRun) + with RunSetup(modDir, [fileToRun] + args): + instrument.setupFinder(modDir) + modName = os.path.basename(os.path.splitext(fileToRun)[0]) + if doTypecheck: + globals['wrapTypecheck'] = typecheck.wrapTypecheck 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 + globals['wrapTypecheck'] = typecheck.wrapNoTypecheck + sys.dont_write_bytecode = True # FIXME: remove + runpy.run_module(modName, init_globals=globals, run_name=modName) -def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, useUntypy=True, extraDirs=None): - doRun = lambda: runCode(fileToRun, globals, args, useUntypy=useUntypy, extraDirs=extraDirs) +def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): + doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, extraDirs=extraDirs) if onlyCheckRunnable: try: doRun() @@ -341,18 +336,18 @@ def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, useUntypy=True, doRun() # globals already contain libDefs -def runTestsInFile(testFile, globals, libDefs, useUntypy=True, extraDirs=[]): +def runTestsInFile(testFile, globals, libDefs, doTypecheck=True, extraDirs=[]): printStderr() printStderr(f"Running tutor's tests in {testFile}") libDefs.resetTestCount() try: - runCode(testFile, globals, [], useUntypy=useUntypy, extraDirs=extraDirs) + runCode(testFile, globals, [], doTypecheck=doTypecheck, 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): +def performChecks(check, testFile, globals, libDefs, doTypecheck=True, extraDirs=None, loadingFailed=False): prefix = '' if check and testFile: prefix = 'Student: ' @@ -360,7 +355,7 @@ def performChecks(check, testFile, globals, libDefs, useUntypy=True, extraDirs=N if check: testResultsInstr = {'total': 0, 'failing': 0} if testFile: - testResultsInstr = runTestsInFile(testFile, globals, libDefs, useUntypy=useUntypy, + testResultsInstr = runTestsInFile(testFile, globals, libDefs, doTypecheck=doTypecheck, extraDirs=extraDirs) failingSum = testResultsStudent['failing'] + testResultsInstr['failing'] die(0 if failingSum < 1 else 1) @@ -380,43 +375,49 @@ def enterInteractive(userDefs): globals()[k] = v print() -def tbToFrameList(tb): +def tbToFrameList(tb: types.TracebackType) -> list[types.FrameType]: cur = tb - res = [] + res: list[types.FrameType] = [] while cur: res.append(cur.tb_frame) cur = cur.tb_next return res -def isWyppFrame(frame): +def isCallWithFramesRemoved(frame: types.FrameType): + return frame.f_code.co_name == '_call_with_frames_removed' + +def isWyppFrame(frame: types.FrameType): modName = frame.f_globals["__name__"] return '__wypp_runYourProgram' in frame.f_globals or \ - modName == 'untypy' or modName.startswith('untypy.') or \ + modName == 'typeguard' or modName.startswith('typeguard.') 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)] +def isRunpyFrame(frame: types.FrameType): + return frame.f_code.co_filename == '' + +# Returns a StackSummary object. Filters the trackback by removing leading wypp or typeguard +# frames and by removing trailing frames behind _call_with_frames_removed +def limitTraceback(frameList: list[types.FrameType], isBug: bool) -> traceback.StackSummary: + if not isBug: + endIdx = len(frameList) + for i in range(endIdx - 1, 0, -1): + if isCallWithFramesRemoved(frameList[i]): + endIdx = i - 1 + break + frameList = utils.dropWhile(frameList[:endIdx], lambda f: isWyppFrame(f) or isRunpyFrame(f)) + frames = [(f, f.f_lineno) for f in frameList] 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) + frameList = tbToFrameList(tb) if tb is not None else [] 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, errors.WyppError) isBug = not isWyppError and not isinstance(val, SyntaxError) and \ - not isinstance(val, errors.DeliberateError) and frameList \ + not isinstance(val, errors.DeliberateError) and len(frameList) > 0 \ and isWyppFrame(frameList[-1]) stackSummary = limitTraceback(frameList, isBug) header = False @@ -426,7 +427,7 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): header = True file.write(x) if isWyppError: - name = 'Wypp' + val.simpleName() + name = 'Wypp' + val.simpleName() # type: ignore file.write(name) s = str(val) if s and s[0] != '\n': @@ -453,13 +454,13 @@ def getHistoryFilePath(): 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 +# We cannot import typeguard at the top of the file because we might have to install it first. +def importTypeguard(): + global typeguard try: - import untypy + import typeguard # type: ignore except ModuleNotFoundError as e: - printStderr(f"Module untypy not found, sys.path={sys.path}: {e}") + printStderr(f"Module typeguard not found, sys.path={sys.path}: {e}") die(1) requiredVersion = (3, 12, 0) @@ -498,12 +499,12 @@ def main(globals, argList=None): 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.") + if not site.ENABLE_USER_SITE or site.USER_SITE is None: + printStderr(f"User site-packages disabled ({site.USER_SITE}. This might cause problems importing wypp or typeguard.") else: verbose(f"Adding user site-package directory {site.USER_SITE} to sys.path") sys.path.append(site.USER_SITE) - importUntypy() + importTypeguard() fileToRun = args.file if args.changeDir: @@ -518,7 +519,7 @@ def main(globals, argList=None): if fileToRun is None: return if not args.checkRunnable and (not args.quiet or args.verbose): - printWelcomeString(fileToRun, version, useUntypy=args.checkTypes) + printWelcomeString(fileToRun, version, doTypecheck=args.checkTypes) libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes) @@ -529,13 +530,13 @@ def main(globals, argList=None): verbose(f'running code in {fileToRun}') globals['__file__'] = fileToRun runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, - useUntypy=args.checkTypes, extraDirs=args.extraDirs) + doTypecheck=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, + performChecks(args.check, args.testFile, globals, libDefs, doTypecheck=args.checkTypes, extraDirs=args.extraDirs, loadingFailed=loadingFailed) if isInteractive: @@ -563,27 +564,29 @@ def main(globals, argList=None): 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 + pass + + # FIXME + # 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/src/typecheck.py b/python/src/typecheck.py index def6dc2b..09bf34dc 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -57,9 +57,14 @@ def wrapTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: def _wrap(f: Callable[P, T]) -> Callable[P, T]: sig = inspect.signature(f) def wrapped(*args, **kwargs) -> T: - checkArguments(sig, args, kwargs, checkCfg) - result = utils._call_with_frames_removed(f, *args, **kwargs) - checkReturn(sig, result) + utils._call_with_frames_removed(checkArguments, sig, args, kwargs, checkCfg) + result = f(*args, **kwargs) + utils._call_with_frames_removed(checkReturn, sig, result) return result return wrapped return _wrap + +def wrapNoTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: + def _wrap(f: Callable[P, T]) -> Callable[P, T]: + return f + return _wrap diff --git a/python/src/utils.py b/python/src/utils.py index 16e1288f..fbbaca7e 100644 --- a/python/src/utils.py +++ b/python/src/utils.py @@ -10,7 +10,6 @@ def _call_with_frames_removed( ) -> T: return f(*args, **kwargs) - def getEnv(name, conv, default): s = os.getenv(name) if s is None: @@ -19,3 +18,11 @@ def getEnv(name, conv, default): 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 + return l[i:] diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index 0261006f..ead27985 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -108,7 +108,7 @@ def _patchDataClass(cls, mutable): cls.__kind = 'record' cls.__init__.__annotations__ = _collectDataClassAttributes(cls) cls.__init__.__original = cls # mark class as source of annotation - cls.__init__ = typecheck.wrapTypecheck(cls.__init__) + cls.__init__ = typecheck.wrapTypecheck({'kind': 'method'})(cls.__init__) if mutable: # prevent new fields being added @@ -125,6 +125,8 @@ def check(v): raise TypeError(f'Expected argument of type {myTypeguard.renderTy(ty)} ' \ f'for attribute {name}, got {myTypeguard.renderTy(type(v))}: {v}') checker[name] = check + else: + raise errors.WyppTypeError(f'No type annotation for attribute {name}') oldSetattr = cls.__setattr__ def _setattr(obj, k, v): From bbbf8486b98d3c762a9c6fef645893f355b72598 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 12 Sep 2025 13:27:56 +0200 Subject: [PATCH 15/56] many improvements --- python/TODO_nowrappers.md | 15 +- python/debug.py | 4 +- python/src/ansi.py | 32 ++ python/src/errors.py | 182 ++++++++++-- python/src/instrument.py | 17 +- python/src/location.py | 273 ++++++++++++++++++ python/src/myLogging.py | 3 + python/src/runner.py | 50 +--- python/src/runner3.py | 24 -- python/src/stacktrace.py | 88 ++++++ python/src/typecheck.py | 92 ++++-- python/src/writeYourProgram.py | 15 +- .../test-data-2.0/constructorDataclassOk.py | 8 + python/test-data-2.0/functionArg.py | 5 +- python/test-data-2.0/staticmethod.py | 6 + python/test-data-2.0/staticmethod_ok.py | 7 + python/tests/sample.py | 5 +- 17 files changed, 697 insertions(+), 129 deletions(-) create mode 100644 python/src/ansi.py create mode 100644 python/src/location.py delete mode 100644 python/src/runner3.py create mode 100644 python/src/stacktrace.py create mode 100644 python/test-data-2.0/constructorDataclassOk.py create mode 100644 python/test-data-2.0/staticmethod.py create mode 100644 python/test-data-2.0/staticmethod_ok.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 90e0d031..a55f5e20 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,5 +1,10 @@ -* support for records is missing -* partial annotations are supported, not flagged as an error. - * main problem: no return type annotation is not interprete as 'None' -* iterator broken -* check: caching in __pycache__ could hurt us when the instrumentation code changes +* ./run test-data-2.0/forwardRefs_ok.py +* Finetuning error messages +* Unit tests: + * FunctionCode class + * locationOfArgument + * ReturnTracker +* German translation of error messages? +* Fix unit tests in test-data +* Typechecked console +* Debug slow startup times diff --git a/python/debug.py b/python/debug.py index 5ffdb0cb..f3edd5e2 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. @@ -13,5 +13,5 @@ sys.path.insert(0, os.path.join(thisDir, 'site-lib')) -from wypp import runner +from wypp import runner # type: ignore runner.main(globals(), args) diff --git a/python/src/ansi.py b/python/src/ansi.py new file mode 100644 index 00000000..a919d0c0 --- /dev/null +++ b/python/src/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/src/errors.py b/python/src/errors.py index fa3a3012..22e3cae1 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -1,29 +1,177 @@ +from __future__ import annotations from typing import * from dataclasses import dataclass import abc +import inspect +import location +from myTypeguard import renderTy -@dataclass -class Pos: - line: int - col: int +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 ordinal(i: int) -> str: + i = i + 1 # number starts at zero + match i: + case 1: return '1st' + case 2: return '2nd' + case 3: return '3rd' + case _: return f'{i}th' + +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 @dataclass -class Location: - file: str - startPos: Pos - endPos: Pos +class CallableName: + name: str + kind: location.CallableKind -class WyppError(abc.ABC): - pass + def __str__(self): + match self.kind: + case 'function': + return f'function `{self.name}`' + case location.ClassMember(_, clsName): + return f'method `{self.name}` of class `{clsName}`' + @property + def short(self): + match self.kind: + case 'function': + return 'function' + case location.ClassMember('method', _): + return 'method' + case location.ClassMember('constructor', _): + return 'constructor' -# DeliberateError instances are not reported as bugs -class DeliberateError(WyppError, abc.ABC): - pass +class WyppTypeError(TypeError, WyppError): -class WyppTypeError(TypeError, DeliberateError): + def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []): + WyppError.__init__(self, extraFrames) + self.msg = msg + self.add_note(msg) - # def __init__(self, expectedTy: Any, givenValue: Any) - pass + def __str__(self): + return f'WyppTypeError: {self.msg}' + + @staticmethod + def resultError(callableName: 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('no return found') + else: + lines.append(renderGiven(givenValue, returnLoc)) + lines.append('') + if resultTy is None: + # no result type expected but given + lines.append(f'When executing {callableName}, expecting no return value.') + lines.append( + f'But the {callableName.short} returned a value of type `{renderTy(type(givenValue))}`.' + ) + elif givenValue is None: + # result type expected but none given + lines.append( + f'When executing {callableName}, expecting return value of type `{renderTy(resultTy)}`.' + ) + lines.append('But no return found.') + else: + # result type expected but different type given + lines.append( + f'When executing {callableName}, expecting return value of type `{renderTy(resultTy)}`.' + ) + if not isParameterizedType(resultTy): + lines.append( + f'But the call returned a value of type `{renderTy(type(givenValue))}`.' + ) + if resultTypeLoc and callLoc: + lines.append('') + lines.append(f'## File {resultTypeLoc.filename}') + lines.append(f'## Result type declared in line {resultTypeLoc.startLine}:\n') + lines.append(renderLoc(resultTypeLoc)) + lines.append('') + if returnLoc: + if resultTypeLoc.filename != returnLoc.filename: + lines.append(f'## File {returnLoc.filename}') + lines.append(f'## Problematic return in line {returnLoc.startLine}:\n') + lines.append(renderLoc(returnLoc)) + lines.append('') + if resultTypeLoc.filename != callLoc.filename: + lines.append(f'## File {callLoc.filename}') + lines.append(f'## Call causing the problematic return in line {callLoc.startLine}:\n') + lines.append(renderLoc(callLoc)) + raise WyppTypeError('\n'.join(lines), extraFrames) + + @staticmethod + def argumentError(callableName: CallableName, paramName: str, paramIndex: int, paramLoc: Optional[location.Loc], + paramTy: Any, givenValue: Any, givenLoc: Optional[location.Loc]) -> WyppTypeError: + lines = [] + givenStr = renderGiven(givenValue, givenLoc) + lines.append(givenStr) + lines.append('') + match callableName.kind: + case 'function' | location.ClassMember('method', _): + lines.append( + f'The call of {callableName} expects argument of type `{renderTy(paramTy)}` ' \ + f'as {ordinal(paramIndex)} parameter.' + ) + paramWhat = 'Parameter' + case location.ClassMember('constructor', clsName): + lines.append( + f'When constructing `{clsName}` object, expecting argument of type `{renderTy(paramTy)}` ' \ + f'for {ordinal(paramIndex)} attribute `{paramName}`.' + ) + paramWhat = 'Attribute' + if not isParameterizedType(paramTy): + lines.append(f'But the argument has type `{renderTy(type(givenValue))}`.') + if givenLoc: + lines.append('') + lines.append(f'## File {givenLoc.filename}') + lines.append(f'## Problematic call in line {givenLoc.startLine}:\n') + lines.append(renderLoc(givenLoc)) + if paramLoc: + lines.append('') + if not givenLoc or paramLoc.filename != givenLoc.filename: + lines.append(f'## File {paramLoc.filename}') + lines.append(f'## {paramWhat} type declared in line {paramLoc.startLine}:\n') + lines.append(renderLoc(paramLoc)) + raise WyppTypeError('\n'.join(lines)) + + @staticmethod + def partialAnnotationError(name: str, paramName: str, paramLoc: Optional[location.Loc]) -> WyppTypeError: + lines = [] + lines.append( + f'Expected type annotation for parameter {paramName} of {name}.' + ) + if paramLoc: + lines.append('') + lines.append(f'## File {paramLoc.filename}') + lines.append(f'## Parameter declared in line {paramLoc.startLine}:\n') + lines.append(renderLoc(paramLoc)) + raise WyppTypeError('\n'.join(lines)) -class WyppAttributeError(AttributeError, DeliberateError): +class WyppAttributeError(AttributeError, WyppError): pass diff --git a/python/src/instrument.py b/python/src/instrument.py index 7385fb66..fed712d2 100644 --- a/python/src/instrument.py +++ b/python/src/instrument.py @@ -20,25 +20,26 @@ def parseExp(s: str) -> ast.expr: class Configs: funConfig: ast.expr = parseExp("{'kind': 'function'}") - methodConfig: ast.expr = parseExp("{'kind': 'method'}") + @staticmethod + def methodConfig(clsName: str) -> ast.expr: + return parseExp("{'kind': 'method', 'className': " + repr(clsName) + "}") -def transformStmt(stmt: ast.stmt, insideClass: bool) -> ast.stmt: - # FIXME: static methods - cfg = Configs.methodConfig if insideClass else Configs.funConfig +def transformStmt(stmt: ast.stmt, outerClassName: Optional[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): return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) - case ast.ClassDef(name, bases, keywords, body, decorator_list, type_params): - newBody = [transformStmt(s, insideClass=True) for s in body] - return ast.ClassDef(name, bases, keywords, newBody, decorator_list, type_params) + case ast.ClassDef(className, bases, keywords, body, decorator_list, type_params): + newBody = [transformStmt(s, outerClassName=className) for s in body] + return ast.ClassDef(className, bases, keywords, newBody, decorator_list, type_params) case _: return stmt def transformModule(m: ast.Module | ast.Expression | ast.Interactive) -> ast.Module | ast.Expression | ast.Interactive: match m: case ast.Module(body, type_ignores): - newStmts = [transformStmt(stmt, insideClass=False) for stmt in body] + newStmts = [transformStmt(stmt, outerClassName=None) for stmt in body] return ast.Module(newStmts, type_ignores) case _: return m diff --git a/python/src/location.py b/python/src/location.py new file mode 100644 index 00000000..23f6ce95 --- /dev/null +++ b/python/src/location.py @@ -0,0 +1,273 @@ +from __future__ import annotations +from typing import * +from dataclasses import dataclass +import inspect +import linecache +from traceback import FrameSummary +import dis +import ast +import ansi +import utils +import myLogging +import re +import abc + +@dataclass +class Loc: + filename: str + startLine: int + startCol: Optional[int] + endLine: Optional[int] + endCol: Optional[int] + + 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', 'constructor'] + className: str + +type CallableKind = Literal['function'] | ClassMember + +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 + +# FIXME: write unit tests +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) + try: + self.source = inspect.getsource(f) + except Exception: + self.source = None + if self.source: + try: + self.tree = ast.parse(self.source) + except Exception: + self.tree = None + else: + self.tree = None + self._name = f.__name__ + self.file = f.__code__.co_filename + + @property + def name(self): + return self._name + + def _findDef(self) -> Optional[ast.FunctionDef | ast.AsyncFunctionDef]: + if not self.tree: + return None + for node in ast.walk(self.tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == self.name: + return node + + def getResultTypeLocation(self) -> Optional[Loc]: + """ + Returns the location of the result type + """ + node = self._findDef() + if not node: + return None + assert self.source is not None + r = node.returns + if r: + return Loc(self.file, r.lineno, r.col_offset, r.end_lineno, r.end_col_offset) + else: + funNameRe = re.compile(r'def\s+([^\s()]+)') + lineNo = node.lineno - 1 + colNo = node.col_offset + lines = self.source.split('\n') + if lineNo < len(lines): + line = lines[lineNo][colNo:] + m = funNameRe.match(line) + if m: + name = m.group(1) + i = line.find(name) + nameCol = colNo + i + return Loc(self.file, node.lineno, nameCol, node.lineno, nameCol + len(name)) + 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) + +# FIXME: write unit tests +class RecordConstructorInfo(CallableInfo): + """ + Class giving access to various properties of a record constructor. + """ + def __init__(self, cls: type): + super().__init__(ClassMember('constructor', cls.__name__)) + self.cls = cls + try: + self.source = inspect.getsource(cls) + except Exception: + self.source = None + if self.source: + self.tree = ast.parse(self.source) + else: + self.tree = None + @property + def name(self): + return self.cls.__name__ + def getResultTypeLocation(self) -> Optional[Loc]: + return None + def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: + if not self.tree: + return None + for node in ast.walk(self.tree): + print(node) + if isinstance(node, ast.AnnAssign): + if isinstance(node.target, ast.Name): + file = self.cls.__code__.co_filename + return Loc(file, node.lineno, node.col_offset, node.end_lineno, node.end_col_offset) + + +# FIXME: write unit test +def locationOfArgument(fi: inspect.FrameInfo, idx: int) -> 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 + tree = ast.parse(codeOfCall) + match tree: + case ast.Module([ast.Expr(ast.Call(_fun, args, _kwArgs))]): + if idx >= 0 and idx < len(args): + arg = args[idx] + 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/src/myLogging.py b/python/src/myLogging.py index 30e16cf2..bb1994dc 100644 --- a/python/src/myLogging.py +++ b/python/src/myLogging.py @@ -18,3 +18,6 @@ def verbose(s): def debug(s): if DEBUG: printStderr('[D] ' + str(s)) + +def warn(s: str): + printStderr('WARN: ' + str(s)) diff --git a/python/src/runner.py b/python/src/runner.py index 9ff420a8..6e3181a6 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -15,6 +15,7 @@ import runpy import types from dataclasses import dataclass +import stacktrace # local imports import typecheck @@ -375,51 +376,22 @@ def enterInteractive(userDefs): globals()[k] = v print() -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 == '_call_with_frames_removed' - -def isWyppFrame(frame: types.FrameType): - modName = frame.f_globals["__name__"] - return '__wypp_runYourProgram' in frame.f_globals or \ - modName == 'typeguard' or modName.startswith('typeguard.') or \ - modName == 'wypp' or modName.startswith('wypp.') - -def isRunpyFrame(frame: types.FrameType): - return frame.f_code.co_filename == '' - -# Returns a StackSummary object. Filters the trackback by removing leading wypp or typeguard -# frames and by removing trailing frames behind _call_with_frames_removed -def limitTraceback(frameList: list[types.FrameType], isBug: bool) -> traceback.StackSummary: - if not isBug: - endIdx = len(frameList) - for i in range(endIdx - 1, 0, -1): - if isCallWithFramesRemoved(frameList[i]): - endIdx = i - 1 - break - frameList = utils.dropWhile(frameList[:endIdx], lambda f: isWyppFrame(f) or isRunpyFrame(f)) - frames = [(f, f.f_lineno) for f in frameList] - 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 tb is not None else [] + 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:] - isWyppError = isinstance(val, errors.WyppError) isBug = not isWyppError and not isinstance(val, SyntaxError) and \ - not isinstance(val, errors.DeliberateError) and len(frameList) > 0 \ - and isWyppFrame(frameList[-1]) - stackSummary = limitTraceback(frameList, isBug) + len(frameList) > 0 and stacktrace.isWyppFrame(frameList[-1]) + stackSummary = stacktrace.limitTraceback(frameList, extra, not isBug and not DEBUG) header = False for x in stackSummary.format(): if not header: @@ -427,11 +399,9 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): header = True file.write(x) if isWyppError: - name = 'Wypp' + val.simpleName() # type: ignore - file.write(name) s = str(val) if s and s[0] != '\n': - file.write(': ') + file.write('\n') file.write(s) file.write('\n') else: diff --git a/python/src/runner3.py b/python/src/runner3.py deleted file mode 100644 index 45938675..00000000 --- a/python/src/runner3.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations -import sys -import os -import instrument -import typecheck -import runner as r -import os -import runpy - -def main(globals, argList=None): - print('runner3') - (args, restArgs) = r.parseCmdlineArgs(argList) - fileToRun = args.file - if args.changeDir: - os.chdir(os.path.dirname(fileToRun)) - fileToRun = os.path.basename(fileToRun) - modDir = os.path.dirname(fileToRun) - instrument.setupFinder(modDir) - sys.path.insert(0, modDir) - modName = os.path.basename(os.path.splitext(fileToRun)[0]) - globals['wrapTypecheck'] = typecheck.wrapTypecheck - print(f'Running module {modName}, file={fileToRun}') - sys.dont_write_bytecode = True - runpy.run_module(modName, init_globals=globals, run_name=modName) diff --git a/python/src/stacktrace.py b/python/src/stacktrace.py new file mode 100644 index 00000000..c913e814 --- /dev/null +++ b/python/src/stacktrace.py @@ -0,0 +1,88 @@ +import types +import traceback +import utils +import inspect +from typing import Optional, Any +import os +import sys + +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 == '_call_with_frames_removed' + +def isWyppFrame(frame: types.FrameType): + modName = frame.f_globals.get("__name__") or '__wypp__' + return '__wypp_runYourProgram' in frame.f_globals or \ + modName == 'typeguard' or modName.startswith('typeguard.') or \ + modName == 'wypp' or modName.startswith('wypp.') + +def isRunpyFrame(frame: types.FrameType): + return frame.f_code.co_filename == '' + +# Returns a StackSummary object. Filters the trackback by removing leading wypp or typeguard +# frames and by removing trailing frames behind _call_with_frames_removed. +# The first entry is the outermost frame, the last entry in the frame list is the frame +# where the exception happened. +def limitTraceback(frameList: list[types.FrameType], + extraFrames: list[inspect.FrameInfo], + filter: bool) -> traceback.StackSummary: + if filter: + endIdx = len(frameList) + for i in range(endIdx - 1, 0, -1): + if isCallWithFramesRemoved(frameList[i]): + endIdx = i - 1 + break + frameList = utils.dropWhile(frameList[:endIdx], 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() + #for fi in stack: + # print(f'{fi.filename}:{fi.lineno}') + 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 + +# FIXME: unit tests +class ReturnTracker: + def __init__(self): + self.__returnFrame: Optional[types.FrameType] = None + 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 # self.__returnFrame = None + case 'return': + self.__returnFrame = frame + case 'c_call': + pass + case 'c_return': + pass + case 'c_exception': + pass + def getReturnFrame(self) -> Optional[inspect.FrameInfo]: + f = self.__returnFrame + if f: + tb = inspect.getframeinfo(f, context=1) + return inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index) + else: + return None + +def installProfileHook() -> ReturnTracker: + obj = sys.getprofile() + if isinstance(obj, ReturnTracker): + return obj + obj = ReturnTracker() + sys.setprofile(obj) + return obj diff --git a/python/src/typecheck.py b/python/src/typecheck.py index 09bf34dc..584d2ea4 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -1,12 +1,15 @@ from __future__ import annotations +import sys from collections.abc import Callable -from typing import ParamSpec -from typing import TypeVar, Any +from typing import ParamSpec, TypeVar, Any, Optional, Literal import inspect import typing from dataclasses import dataclass import utils from myTypeguard import matchesTy, renderTy +import stacktrace +import location +import errors def printVars(what: str, *l): s = what + ": " + ', '.join([str(x) for x in l]) @@ -15,55 +18,104 @@ def printVars(what: str, *l): def isEmptyAnnotation(t: Any) -> bool: return t is inspect.Signature.empty or t is inspect.Parameter.empty -def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, cfg: CheckCfg) -> None: +def mkCallableName(c: location.CallableInfo) -> errors.CallableName: + return errors.CallableName(c.name, c.kind) + +def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, + code: location.CallableInfo, cfg: CheckCfg) -> None: params = list(sig.parameters) if len(params) != len(args): raise TypeError(f"Expected {len(params)} arguments, got {len(args)}") + kind: Literal['function', 'method', 'staticmethod'] = 'function' + match cfg.kind: + case location.ClassMember('constructor', _): + kind = 'method' + case location.ClassMember('method', _): + if len(params) > 0 and params[0] == 'self': + kind = 'method' + else: + kind = 'staticmethod' + case 'function': + kind = 'function' + offset = 1 if kind == 'method' else 0 for i in range(len(args)): name = params[i] - t = sig.parameters[name].annotation - if isEmptyAnnotation(t): - if i == 0 and cfg.kind == 'method': - if name != 'self': - raise TypeError(f'Name of first parameter of method {name} must be self not {name}') - else: - raise TypeError(f'Missing type for parameter {name}') + p = sig.parameters[name] + t = p.annotation + if i == 0 and kind == 'method' and isEmptyAnnotation(t): + pass + elif i != 0 and isEmptyAnnotation(t): + locDecl = code.getParamSourceLocation(name) + raise errors.WyppTypeError.partialAnnotationError(code.name, name, locDecl) else: a = args[i] if not matchesTy(a, t): - raise TypeError(f'Expected argument of type {renderTy(t)} for parameter {name}, got {renderTy(type(a))}: {a}') + fi = stacktrace.callerOutsideWypp() + if fi is not None: + locArg = location.locationOfArgument(fi, i) + else: + locArg = None + locDecl = code.getParamSourceLocation(name) + raise errors.WyppTypeError.argumentError(mkCallableName(code), name, i - offset, locDecl, t, a, locArg) -def checkReturn(sig: inspect.Signature, result: Any) -> None: +def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo], + result: Any, code: location.CallableInfo) -> None: t = sig.return_annotation if isEmptyAnnotation(t): t = None if not matchesTy(result, t): - print(t) - raise TypeError(f"Expected return value of type {renderTy(t)}, got {renderTy(type(result))}: {result}") + fi = stacktrace.callerOutsideWypp() + if fi is not None: + locRes = location.Loc.fromFrameInfo(fi) + locDecl = code.getResultTypeLocation() + returnLoc = None + extraFrames = [] + if returnFrame: + returnLoc = location.Loc.fromFrameInfo(returnFrame) + extraFrames = [returnFrame] + raise errors.WyppTypeError.resultError(mkCallableName(code), locDecl, t, returnLoc, result, + locRes, extraFrames) + -FunKind = typing.Literal['function', 'method', 'staticmethod'] @dataclass class CheckCfg: - kind: FunKind + kind: location.CallableKind @staticmethod def fromDict(d: dict) -> CheckCfg: - return CheckCfg(kind=d['kind']) + k = d['kind'] + match k: + case 'function': + kind = 'function' + case 'method': + kind = location.ClassMember('method', d['className']) + return CheckCfg(kind=kind) P = ParamSpec("P") T = TypeVar("T") -def wrapTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: +def wrapTypecheck(cfg: dict, outerCode: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: checkCfg = CheckCfg.fromDict(cfg) def _wrap(f: Callable[P, T]) -> Callable[P, T]: sig = inspect.signature(f) + if outerCode is None: + code = location.StdCallableInfo(f, checkCfg.kind) + else: + code = outerCode def wrapped(*args, **kwargs) -> T: - utils._call_with_frames_removed(checkArguments, sig, args, kwargs, checkCfg) + returnTracker = stacktrace.installProfileHook() + utils._call_with_frames_removed(checkArguments, sig, args, kwargs, code, checkCfg) result = f(*args, **kwargs) - utils._call_with_frames_removed(checkReturn, sig, result) + utils._call_with_frames_removed( + checkReturn, sig, returnTracker.getReturnFrame(), result, code + ) return result return wrapped return _wrap +def wrapTypecheckRecordConstructor(cls: type) -> Callable: + code = location.RecordConstructorInfo(cls) + return wrapTypecheck({'kind': 'method', 'className': cls.__name__}, code)(cls.__init__) + def wrapNoTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: def _wrap(f: Callable[P, T]) -> Callable[P, T]: return f diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index ead27985..5e4106b7 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -1,4 +1,3 @@ -# FIXME: make exceptions nicer import typing import dataclasses import inspect @@ -99,7 +98,7 @@ def _collectDataClassAttributes(cls): return result -def _patchDataClass(cls, mutable): +def _patchDataClass(cls, mutable: bool): fieldNames = [f.name for f in dataclasses.fields(cls)] setattr(cls, EQ_ATTRS_ATTR, fieldNames) @@ -107,8 +106,7 @@ def _patchDataClass(cls, mutable): # 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__ = typecheck.wrapTypecheck({'kind': 'method'})(cls.__init__) + cls.__init__ = typecheck.wrapTypecheckRecordConstructor(cls) if mutable: # prevent new fields being added @@ -140,8 +138,9 @@ def _setattr(obj, k, v): setattr(cls, "__setattr__", _setattr) return cls -def record(cls=None, mutable=False): - def wrap(cls): +@typing.dataclass_transform() +def record(cls, mutable=False): + def wrap(cls: type): newCls = dataclasses.dataclass(cls, frozen=not mutable) if _typeCheckingEnabled: return _patchDataClass(newCls, mutable) @@ -359,7 +358,7 @@ def deepEq(v1, v2, **flags): return False # v1 == v2 already checked return False -class TodoError(Exception, errors.DeliberateError): +class TodoError(Exception, errors.WyppError): pass def todo(msg=None): @@ -367,7 +366,7 @@ def todo(msg=None): msg = 'TODO' raise TodoError(msg) -class ImpossibleError(Exception, errors.DeliberateError): +class ImpossibleError(Exception, errors.WyppError): pass def impossible(msg=None): diff --git a/python/test-data-2.0/constructorDataclassOk.py b/python/test-data-2.0/constructorDataclassOk.py new file mode 100644 index 00000000..5f2c3bec --- /dev/null +++ b/python/test-data-2.0/constructorDataclassOk.py @@ -0,0 +1,8 @@ +#from dataclasses import dataclass +from wypp import * + +@dataclass +class C: + x: int + +c = C("1") diff --git a/python/test-data-2.0/functionArg.py b/python/test-data-2.0/functionArg.py index 75d82801..9fcf0bd2 100644 --- a/python/test-data-2.0/functionArg.py +++ b/python/test-data-2.0/functionArg.py @@ -1,4 +1,5 @@ -def foo(i: int) -> int: +def foo(i: int, j: int) -> int: return i + 1 -foo("1") +if True: + print(foo(foo(1, "1"), 42)) diff --git a/python/test-data-2.0/staticmethod.py b/python/test-data-2.0/staticmethod.py new file mode 100644 index 00000000..c177637c --- /dev/null +++ b/python/test-data-2.0/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-2.0/staticmethod_ok.py b/python/test-data-2.0/staticmethod_ok.py new file mode 100644 index 00000000..90b56179 --- /dev/null +++ b/python/test-data-2.0/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/tests/sample.py b/python/tests/sample.py index f020c989..a4e24810 100644 --- a/python/tests/sample.py +++ b/python/tests/sample.py @@ -17,8 +17,7 @@ def canDrink(d: Drink) -> int: # - a circle (Circle) # - a square (Square) # - an overlay of two shapes (Overlay) -Shape = typing.Union[typing.ForwardRef('Circle'), typing.ForwardRef('Square'), typing.ForwardRef('Overlay')] -Shape = typing.Union['Circle', 'Square', 'Overlay'] +type Shape = typing.Union['Circle', 'Square', 'Overlay'] # A point consists of # - x (float) @@ -121,4 +120,4 @@ def pointInShape(point: Point, shape: Shape) -> bool: elif type(shape) == Overlay: return pointInShape(point, shape.top) or pointInShape(point, shape.bottom) else: - uncoveredCase() + return uncoveredCase() From c6a3069a818750480f3a894472d3c0af3291ede3 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 12 Sep 2025 15:45:28 +0200 Subject: [PATCH 16/56] typeguard: properly format forward references --- python/deps/typeguard/_checkers.py | 6 +++++- python/deps/typeguard/_functions.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/python/deps/typeguard/_checkers.py b/python/deps/typeguard/_checkers.py index eb6ac878..32de89de 100644 --- a/python/deps/typeguard/_checkers.py +++ b/python/deps/typeguard/_checkers.py @@ -885,6 +885,9 @@ def check_paramspec( ) -> 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 check_type_internal( value: Any, @@ -915,8 +918,9 @@ def check_type_internal( 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 diff --git a/python/deps/typeguard/_functions.py b/python/deps/typeguard/_functions.py index ca21c14c..e3a2622d 100644 --- a/python/deps/typeguard/_functions.py +++ b/python/deps/typeguard/_functions.py @@ -24,7 +24,6 @@ T = TypeVar("T") TypeCheckFailCallback: TypeAlias = Callable[[TypeCheckError, TypeCheckMemo], Any] - @overload def check_type( value: object, @@ -33,6 +32,7 @@ def check_type( forward_ref_policy: ForwardRefPolicy = ..., typecheck_fail_callback: TypeCheckFailCallback | None = ..., collection_check_strategy: CollectionCheckStrategy = ..., + ns: tuple[dict, dict] | None = None, ) -> T: ... @@ -44,6 +44,7 @@ def check_type( forward_ref_policy: ForwardRefPolicy = ..., typecheck_fail_callback: TypeCheckFailCallback | None = ..., collection_check_strategy: CollectionCheckStrategy = ..., + ns: tuple[dict, dict] | None = None, ) -> Any: ... @@ -58,6 +59,7 @@ def check_type( collection_check_strategy: CollectionCheckStrategy = ( TypeCheckConfiguration().collection_check_strategy ), + ns: tuple[dict, dict] | None = None, ) -> Any: """ Ensure that ``value`` matches ``expected_type``. @@ -101,8 +103,11 @@ def check_type( if _suppression.type_checks_suppressed or expected_type is Any: return value - frame = sys._getframe(1) - memo = TypeCheckMemo(frame.f_globals, frame.f_locals, config=config) + 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) try: check_type_internal(value, expected_type, memo) except TypeCheckError as exc: From fcba062c6854f0231c0684090bca9cce9dbd8fa9 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 12 Sep 2025 15:45:41 +0200 Subject: [PATCH 17/56] typeguard: hide certain prefixes --- python/deps/typeguard/_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/deps/typeguard/_utils.py b/python/deps/typeguard/_utils.py index f61b94d2..72b9b861 100644 --- a/python/deps/typeguard/_utils.py +++ b/python/deps/typeguard/_utils.py @@ -52,7 +52,7 @@ def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any: raise -def get_type_name(type_: Any) -> str: +def get_type_name(type_: Any, noPrefix: list[str]=[]) -> str: name: str for attrname in "__name__", "_name", "__forward_arg__": candidate = getattr(type_, attrname, None) @@ -84,7 +84,7 @@ def get_type_name(type_: Any) -> str: module = type_.__forward_module__ else: module = getattr(type_, "__module__", None) - if module and module not in (None, "typing", "typing_extensions", "builtins"): + if module and module not in (None, "typing", "typing_extensions", "builtins") + tuple(noPrefix): name = module + "." + name return name From 1fea0ff6f25b9832249e3e9b6c94a9979511b36e Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 12 Sep 2025 15:46:12 +0200 Subject: [PATCH 18/56] more fixes --- python/src/myTypeguard.py | 25 ++++++++++++++++++++----- python/src/runner.py | 2 +- python/src/typecheck.py | 27 +++++++++++++++++++-------- python/test-data-2.0/forwardRefs.py | 12 ++++++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 python/test-data-2.0/forwardRefs.py diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py index d7bdd59f..677478fb 100644 --- a/python/src/myTypeguard.py +++ b/python/src/myTypeguard.py @@ -1,16 +1,31 @@ # Wrapper module for typeguard. Do not import typeguard directly but always via myTypeguard - -from typing import Any +from __future__ import annotations +from typing import * # We externally adjust the PYTHONPATH so that the typeguard module can be resolved import typeguard # type: ignore +from dataclasses import dataclass +import sys + +@dataclass(frozen=True) +class Namespaces: + globals: dict + locals: dict + @staticmethod + def empty() -> Namespaces: + return Namespaces({}, {}) -def matchesTy(a: Any, ty: Any) -> bool: +def matchesTy(a: Any, ty: Any, ns: Namespaces) -> bool: try: - typeguard.check_type(a, ty, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS) + 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 def renderTy(t: Any) -> str: - return typeguard._utils.get_type_name(t) + if isinstance(t, str): + return t + return typeguard._utils.get_type_name(t, ['__wypp__']) diff --git a/python/src/runner.py b/python/src/runner.py index 6e3181a6..c0f21535 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -322,7 +322,7 @@ def runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): else: globals['wrapTypecheck'] = typecheck.wrapNoTypecheck sys.dont_write_bytecode = True # FIXME: remove - runpy.run_module(modName, init_globals=globals, run_name=modName) + runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=True) def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, extraDirs=extraDirs) diff --git a/python/src/typecheck.py b/python/src/typecheck.py index 584d2ea4..fd2e560d 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -6,7 +6,7 @@ import typing from dataclasses import dataclass import utils -from myTypeguard import matchesTy, renderTy +from myTypeguard import matchesTy, Namespaces import stacktrace import location import errors @@ -49,7 +49,7 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, raise errors.WyppTypeError.partialAnnotationError(code.name, name, locDecl) else: a = args[i] - if not matchesTy(a, t): + if not matchesTy(a, t, cfg.ns): fi = stacktrace.callerOutsideWypp() if fi is not None: locArg = location.locationOfArgument(fi, i) @@ -59,11 +59,11 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, raise errors.WyppTypeError.argumentError(mkCallableName(code), name, i - offset, locDecl, t, a, locArg) def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo], - result: Any, code: location.CallableInfo) -> None: + result: Any, code: location.CallableInfo, cfg: CheckCfg) -> None: t = sig.return_annotation if isEmptyAnnotation(t): t = None - if not matchesTy(result, t): + if not matchesTy(result, t, cfg.ns): fi = stacktrace.callerOutsideWypp() if fi is not None: locRes = location.Loc.fromFrameInfo(fi) @@ -77,9 +77,10 @@ def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo] locRes, extraFrames) -@dataclass +@dataclass(frozen=True) class CheckCfg: kind: location.CallableKind + ns: Namespaces @staticmethod def fromDict(d: dict) -> CheckCfg: k = d['kind'] @@ -88,14 +89,24 @@ def fromDict(d: dict) -> CheckCfg: kind = 'function' case 'method': kind = location.ClassMember('method', d['className']) - return CheckCfg(kind=kind) + return CheckCfg(kind=kind, ns=Namespaces.empty()) + def setNamespaces(self, ns: Namespaces) -> CheckCfg: + return CheckCfg(kind=self.kind, ns=ns) P = ParamSpec("P") T = TypeVar("T") +def getNamespacesOfCallable(func: Callable): + globals = func.__globals__ + # if it's a method, let it see the owning class namespace + owner = getattr(func, "__qualname__", "").split(".")[0] + locals = globals.get(owner, {}) + return Namespaces(globals, locals) + def wrapTypecheck(cfg: dict, outerCode: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: - checkCfg = CheckCfg.fromDict(cfg) + outerCheckCfg = CheckCfg.fromDict(cfg) def _wrap(f: Callable[P, T]) -> Callable[P, T]: + checkCfg = outerCheckCfg.setNamespaces(getNamespacesOfCallable(f)) sig = inspect.signature(f) if outerCode is None: code = location.StdCallableInfo(f, checkCfg.kind) @@ -106,7 +117,7 @@ def wrapped(*args, **kwargs) -> T: utils._call_with_frames_removed(checkArguments, sig, args, kwargs, code, checkCfg) result = f(*args, **kwargs) utils._call_with_frames_removed( - checkReturn, sig, returnTracker.getReturnFrame(), result, code + checkReturn, sig, returnTracker.getReturnFrame(), result, code, checkCfg ) return result return wrapped diff --git a/python/test-data-2.0/forwardRefs.py b/python/test-data-2.0/forwardRefs.py new file mode 100644 index 00000000..cd11bd03 --- /dev/null +++ b/python/test-data-2.0/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) From 456d6a16db8076f93f68c2631819122d5a54b780 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sat, 13 Sep 2025 10:58:50 +0200 Subject: [PATCH 19/56] some I18N --- python/TODO_nowrappers.md | 4 +- python/src/errors.py | 101 +++++++------------------- python/src/i18n.py | 148 ++++++++++++++++++++++++++++++++++++++ python/src/location.py | 8 +++ python/src/typecheck.py | 15 ++-- 5 files changed, 191 insertions(+), 85 deletions(-) create mode 100644 python/src/i18n.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index a55f5e20..61391c6b 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,10 +1,8 @@ -* ./run test-data-2.0/forwardRefs_ok.py -* Finetuning error messages +* German translation of error messages * Unit tests: * FunctionCode class * locationOfArgument * ReturnTracker -* German translation of error messages? * Fix unit tests in test-data * Typechecked console * Debug slow startup times diff --git a/python/src/errors.py b/python/src/errors.py index 22e3cae1..970d2f9d 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -1,9 +1,9 @@ from __future__ import annotations from typing import * -from dataclasses import dataclass import abc import inspect import location +import i18n from myTypeguard import renderTy class WyppError(abc.ABC): @@ -14,14 +14,6 @@ def renderLoc(loc: location.Loc) -> str: s = '\n'.join([l.highlight() for l in location.highlightedLines(loc)]) return s.rstrip() -def ordinal(i: int) -> str: - i = i + 1 # number starts at zero - match i: - case 1: return '1st' - case 2: return '2nd' - case 3: return '3rd' - case _: return f'{i}th' - def renderStr(s: str, quote: str): x = repr(s) return quote + x[1:-1] + quote @@ -44,27 +36,6 @@ 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 -@dataclass -class CallableName: - name: str - kind: location.CallableKind - - def __str__(self): - match self.kind: - case 'function': - return f'function `{self.name}`' - case location.ClassMember(_, clsName): - return f'method `{self.name}` of class `{clsName}`' - @property - def short(self): - match self.kind: - case 'function': - return 'function' - case location.ClassMember('method', _): - return 'method' - case location.ClassMember('constructor', _): - return 'constructor' - class WyppTypeError(TypeError, WyppError): def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []): @@ -76,7 +47,7 @@ def __str__(self): return f'WyppTypeError: {self.msg}' @staticmethod - def resultError(callableName: CallableName, resultTypeLoc: Optional[location.Loc], resultTy: Any, + 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: @@ -88,88 +59,66 @@ def resultError(callableName: CallableName, resultTypeLoc: Optional[location.Loc lines.append('') if resultTy is None: # no result type expected but given - lines.append(f'When executing {callableName}, expecting no return value.') - lines.append( - f'But the {callableName.short} returned a value of type `{renderTy(type(givenValue))}`.' - ) + lines.append(i18n.expectingNoReturn(callableName)) + lines.append(i18n.wrongReturnValue(renderTy(type(givenValue)))) elif givenValue is None: # result type expected but none given - lines.append( - f'When executing {callableName}, expecting return value of type `{renderTy(resultTy)}`.' - ) - lines.append('But no return found.') + lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy))) + lines.append(i18n.noReturnValue()) else: # result type expected but different type given - lines.append( - f'When executing {callableName}, expecting return value of type `{renderTy(resultTy)}`.' - ) + lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy))) if not isParameterizedType(resultTy): - lines.append( - f'But the call returned a value of type `{renderTy(type(givenValue))}`.' - ) + lines.append(i18n.wrongReturnValue(renderTy(givenValue))) if resultTypeLoc and callLoc: lines.append('') - lines.append(f'## File {resultTypeLoc.filename}') - lines.append(f'## Result type declared in line {resultTypeLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {resultTypeLoc.filename}') + lines.append(f'## {i18n.tr("Result type declared in line")} {resultTypeLoc.startLine}:\n') lines.append(renderLoc(resultTypeLoc)) lines.append('') if returnLoc: if resultTypeLoc.filename != returnLoc.filename: - lines.append(f'## File {returnLoc.filename}') - lines.append(f'## Problematic return in line {returnLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {returnLoc.filename}') + lines.append(f'## {i18n.tr("Problematic return in line")} {returnLoc.startLine}:\n') lines.append(renderLoc(returnLoc)) lines.append('') if resultTypeLoc.filename != callLoc.filename: - lines.append(f'## File {callLoc.filename}') - lines.append(f'## Call causing the problematic return in line {callLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {callLoc.filename}') + lines.append(f'## {i18n.tr("Call causing the problematic return in line")} {callLoc.startLine}:\n') lines.append(renderLoc(callLoc)) raise WyppTypeError('\n'.join(lines), extraFrames) @staticmethod - def argumentError(callableName: CallableName, paramName: str, paramIndex: int, paramLoc: Optional[location.Loc], + def argumentError(callableName: location.CallableName, paramName: str, paramIndex: int, paramLoc: Optional[location.Loc], paramTy: Any, givenValue: Any, givenLoc: Optional[location.Loc]) -> WyppTypeError: lines = [] givenStr = renderGiven(givenValue, givenLoc) lines.append(givenStr) lines.append('') - match callableName.kind: - case 'function' | location.ClassMember('method', _): - lines.append( - f'The call of {callableName} expects argument of type `{renderTy(paramTy)}` ' \ - f'as {ordinal(paramIndex)} parameter.' - ) - paramWhat = 'Parameter' - case location.ClassMember('constructor', clsName): - lines.append( - f'When constructing `{clsName}` object, expecting argument of type `{renderTy(paramTy)}` ' \ - f'for {ordinal(paramIndex)} attribute `{paramName}`.' - ) - paramWhat = 'Attribute' + lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramIndex + 1)) if not isParameterizedType(paramTy): - lines.append(f'But the argument has type `{renderTy(type(givenValue))}`.') + lines.append(i18n.realArgumentTy(renderTy(type(givenValue)))) if givenLoc: lines.append('') - lines.append(f'## File {givenLoc.filename}') - lines.append(f'## Problematic call in line {givenLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {givenLoc.filename}') + lines.append(f'## {i18n.tr("Problematic call in line")} {givenLoc.startLine}:\n') lines.append(renderLoc(givenLoc)) if paramLoc: lines.append('') if not givenLoc or paramLoc.filename != givenLoc.filename: - lines.append(f'## File {paramLoc.filename}') - lines.append(f'## {paramWhat} type declared in line {paramLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {paramLoc.filename}') + lines.append(f'## {i18n.tr("Type declared in line")} {paramLoc.startLine}:\n') lines.append(renderLoc(paramLoc)) raise WyppTypeError('\n'.join(lines)) @staticmethod - def partialAnnotationError(name: str, paramName: str, paramLoc: Optional[location.Loc]) -> WyppTypeError: + def partialAnnotationError(callableName: location.CallableName, paramName: str, paramLoc: Optional[location.Loc]) -> WyppTypeError: lines = [] - lines.append( - f'Expected type annotation for parameter {paramName} of {name}.' - ) + lines.append(i18n.expectingTypeAnnotation(callableName, paramName)) if paramLoc: lines.append('') - lines.append(f'## File {paramLoc.filename}') - lines.append(f'## Parameter declared in line {paramLoc.startLine}:\n') + lines.append(f'## {i18n.tr("File")} {paramLoc.filename}') + lines.append(f'## {i18n.tr("Parameter declared in line")} {paramLoc.startLine}:\n') lines.append(renderLoc(paramLoc)) raise WyppTypeError('\n'.join(lines)) diff --git a/python/src/i18n.py b/python/src/i18n.py new file mode 100644 index 00000000..af9f4833 --- /dev/null +++ b/python/src/i18n.py @@ -0,0 +1,148 @@ +from dataclasses import dataclass +import location +from typing import * + +type Lang = Literal['en', 'de'] + +def lang() -> Lang: + return 'de' + +def tr(key, **kws) -> str: + match lang(): + case 'en': + tmpl = key + case 'de': + tmpl = DE[key] + 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 class `{cls}`.': + 'Kein Rückgabewert erwartet bei Aufruf des Konstruktors der Klasse `{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 class `{cls}`.': + 'Rückgabewert vom Typ `{ty} erwartet bei Aufruf des Konstruktors der Klasse `{cls}`.', + + 'But the call returns a value of type `{ty}`.': + 'Aber der Aufruf gibt einen Wert vom Typ `{ty} zurück.', + + 'But no return found.': + 'Aber kein Rückgabewert vorhanden.', + + '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 class `{cls}` expects value of type `{ty}` as {arg}.': + 'Der Aufruf des Konstruktors der Klasse `{cls}` erwartet Wert vom Typ `{ty}` als {arg}.', + + 'But the value given has type `{ty}`.': 'Aber der übergebene Wert hat Typ `{ty}`.', + + 'File': 'Datei', + '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 causing the problematic return in line': 'Aufruf, der das fehlerhafte return verursacht, in Zeile', + + 'Parameter `{param}` of function `{fun}` requires a type annotation': + 'Parameter `{param}` der Funktion `{fun}` benötigt eine Typangabe', + 'Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation': + 'Parameter `{param}` der Methode `{fun}` aus Klasse `{cls}` benötigt eine Typangabe', + 'Parameter `{param}` of the constructor of class `{cls}` requires a type annotation': + 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe', +} + +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('constructor', cls): + return tr('Expecting no return value when calling constructor of class `{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 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('constructor', cls): + return tr('Expecting return value of type `{ty}` when calling constructor of class `{cls}`', + cls=cls, ty=ty) + raise ValueError(f'Unexpected: {cn}') + +def noReturnValue() -> str: + return tr('But no return found.') + +def transArg(pos: int): + match lang(): + case 'en': + match pos: + case 1: '1st argument' + case 2: '2nd argument' + case 3: '3rd argument' + case _: f'{pos}th argument' + case 'de': + match pos: + case 1: 'erstes Argument' + case 2: 'zweites Argument' + case 3: 'drittes Argument' + case 4: 'viertes Argument' + case 5: 'fünftes Argument' + case 6: 'sechstes Argument' + case 7: 'siebtes Argument' + case 8: 'achtes Argument' + case 9: 'neuntes Argument' + case _: f'{pos}. Argument' + + +def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int) -> str: + arg = transArg(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('constructor', cls): + return tr('The call of method `{method}` of class `{cls}` expects value of type `{ty}` as {arg}.', + cls=cls, ty=ty, arg=arg) + raise ValueError(f'Unexpected: {cn}') + +def realArgumentTy(ty: str) -> str: + return tr('But the value given has 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('constructor', cls): + return tr('Parameter `{param}` of the constructor of class `{cls}` requires a type annotation', + cls=cls, param=param) + raise ValueError(f'Unexpected: {cn}') + +# TODO: write automatic tests to ensure all keys are defined and the all string parameters are defined diff --git a/python/src/location.py b/python/src/location.py index 23f6ce95..1378f52a 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -112,6 +112,14 @@ class ClassMember: 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. diff --git a/python/src/typecheck.py b/python/src/typecheck.py index fd2e560d..8f80ab4f 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -18,9 +18,6 @@ def printVars(what: str, *l): def isEmptyAnnotation(t: Any) -> bool: return t is inspect.Signature.empty or t is inspect.Parameter.empty -def mkCallableName(c: location.CallableInfo) -> errors.CallableName: - return errors.CallableName(c.name, c.kind) - def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, code: location.CallableInfo, cfg: CheckCfg) -> None: params = list(sig.parameters) @@ -46,7 +43,7 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, pass elif i != 0 and isEmptyAnnotation(t): locDecl = code.getParamSourceLocation(name) - raise errors.WyppTypeError.partialAnnotationError(code.name, name, locDecl) + raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(code), name, locDecl) else: a = args[i] if not matchesTy(a, t, cfg.ns): @@ -56,7 +53,13 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, else: locArg = None locDecl = code.getParamSourceLocation(name) - raise errors.WyppTypeError.argumentError(mkCallableName(code), name, i - offset, locDecl, t, a, locArg) + raise errors.WyppTypeError.argumentError(location.CallableName.mk(code), + name, + i - offset, + locDecl, + t, + a, + locArg) def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo], result: Any, code: location.CallableInfo, cfg: CheckCfg) -> None: @@ -73,7 +76,7 @@ def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo] if returnFrame: returnLoc = location.Loc.fromFrameInfo(returnFrame) extraFrames = [returnFrame] - raise errors.WyppTypeError.resultError(mkCallableName(code), locDecl, t, returnLoc, result, + raise errors.WyppTypeError.resultError(location.CallableName.mk(code), locDecl, t, returnLoc, result, locRes, extraFrames) From 37e13975c3ca54780df5ff062f0bfde4bcfefcd8 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sun, 14 Sep 2025 07:14:12 +0200 Subject: [PATCH 20/56] finally remove untypy --- python/trash/untypy/.gitignore | 1 - python/trash/untypy/LICENSE | 21 - python/trash/untypy/README.rst | 43 -- python/trash/untypy/examples/ex01.py | 18 - python/trash/untypy/examples/ex02.py | 26 - python/trash/untypy/examples/ex03.py | 14 - python/trash/untypy/examples/ex04.py | 17 - python/trash/untypy/examples/ex05.py | 30 - python/trash/untypy/examples/ex06.py | 15 - python/trash/untypy/examples/ex09.py | 11 - python/trash/untypy/examples/ex10.py | 17 - python/trash/untypy/examples/mylogfile | 0 python/trash/untypy/requirements.txt | 0 python/trash/untypy/runtests.sh | 4 - python/trash/untypy/setup.py | 12 - python/trash/untypy/test-requirements.txt | 0 python/trash/untypy/test/__init__.py | 3 - python/trash/untypy/test/impl/__init__.py | 0 python/trash/untypy/test/impl/test_any.py | 15 - .../trash/untypy/test/impl/test_callable.py | 78 --- python/trash/untypy/test/impl/test_dict.py | 87 --- .../trash/untypy/test/impl/test_forwardRef.py | 57 -- .../trash/untypy/test/impl/test_generator.py | 167 ------ .../untypy/test/impl/test_interface_dict.py | 235 -------- .../untypy/test/impl/test_interface_set.py | 90 --- python/trash/untypy/test/impl/test_list.py | 348 ------------ python/trash/untypy/test/impl/test_literal.py | 29 - python/trash/untypy/test/impl/test_none.py | 35 -- .../trash/untypy/test/impl/test_optional.py | 49 -- .../trash/untypy/test/impl/test_protocol.py | 280 ---------- python/trash/untypy/test/impl/test_set.py | 171 ------ python/trash/untypy/test/impl/test_simple.py | 160 ------ python/trash/untypy/test/impl/test_str.py | 96 ---- python/trash/untypy/test/impl/test_tuple.py | 156 ------ python/trash/untypy/test/impl/test_union.py | 71 --- .../untypy/test/patching_dummy/__init__.py | 0 .../untypy/test/patching_dummy/a/__init__.py | 5 - .../untypy/test/patching_dummy/a/a_sub.py | 2 - .../test/patching_dummy/argument_types.py | 9 - .../untypy/test/patching_dummy/async_gen.py | 16 - .../untypy/test/patching_dummy/b/__init__.py | 5 - .../untypy/test/patching_dummy/b/b_sub.py | 2 - python/trash/untypy/test/patching_dummy/c.py | 5 - .../test/patching_dummy/patching_classes.py | 6 - .../patching_does_not_change_signature.py | 7 - .../untypy/test/patching_dummy/unpatched.py | 2 - python/trash/untypy/test/test_patching.py | 24 - .../untypy/test/test_standalone_checker.py | 21 - python/trash/untypy/test/util.py | 35 -- .../trash/untypy/test/util_test/__init__.py | 0 .../test/util_test/test_return_traces.py | 36 -- .../untypy/test/util_test/test_wrapper.py | 170 ------ .../untypy/test/util_test/untypy_test_case.py | 30 - python/trash/untypy/untypy/__init__.py | 187 ------- python/trash/untypy/untypy/error.py | 384 ------------- python/trash/untypy/untypy/impl/__init__.py | 108 ---- python/trash/untypy/untypy/impl/alias.py | 12 - python/trash/untypy/untypy/impl/annotated.py | 137 ----- python/trash/untypy/untypy/impl/any.py | 34 -- python/trash/untypy/untypy/impl/callable.py | 277 --------- python/trash/untypy/untypy/impl/choice.py | 66 --- .../trash/untypy/untypy/impl/dummy_delayed.py | 43 -- python/trash/untypy/untypy/impl/generator.py | 128 ----- python/trash/untypy/untypy/impl/generic.py | 98 ---- python/trash/untypy/untypy/impl/interface.py | 87 --- .../untypy/untypy/impl/interfaces/__init__.py | 0 .../untypy/untypy/impl/interfaces/dict.py | 68 --- .../untypy/untypy/impl/interfaces/iterable.py | 25 - .../untypy/untypy/impl/interfaces/list.py | 56 -- .../untypy/untypy/impl/interfaces/sequence.py | 31 -- .../untypy/untypy/impl/interfaces/set.py | 53 -- .../untypy/untypy/impl/interfaces/util.py | 6 - python/trash/untypy/untypy/impl/literal.py | 36 -- python/trash/untypy/untypy/impl/none.py | 27 - python/trash/untypy/untypy/impl/optional.py | 44 -- python/trash/untypy/untypy/impl/protocol.py | 524 ------------------ python/trash/untypy/untypy/impl/simple.py | 68 --- .../untypy/untypy/impl/string_forward_refs.py | 19 - python/trash/untypy/untypy/impl/tuple.py | 103 ---- python/trash/untypy/untypy/impl/union.py | 80 --- python/trash/untypy/untypy/interfaces.py | 105 ---- .../trash/untypy/untypy/patching/__init__.py | 59 -- .../untypy/untypy/patching/ast_transformer.py | 165 ------ .../untypy/untypy/patching/import_hook.py | 65 --- .../untypy/patching/standalone_checker.py | 59 -- python/trash/untypy/untypy/util/__init__.py | 260 --------- python/trash/untypy/untypy/util/condition.py | 130 ----- python/trash/untypy/untypy/util/debug.py | 24 - python/trash/untypy/untypy/util/display.py | 44 -- .../trash/untypy/untypy/util/return_traces.py | 75 --- .../trash/untypy/untypy/util/source_utils.py | 36 -- .../untypy/util/tranformer_combinator.py | 11 - .../trash/untypy/untypy/util/typedfunction.py | 218 -------- python/trash/untypy/untypy/util/typehints.py | 133 ----- python/trash/untypy/untypy/util/wrapper.py | 292 ---------- 95 files changed, 7108 deletions(-) delete mode 100644 python/trash/untypy/.gitignore delete mode 100644 python/trash/untypy/LICENSE delete mode 100644 python/trash/untypy/README.rst delete mode 100644 python/trash/untypy/examples/ex01.py delete mode 100644 python/trash/untypy/examples/ex02.py delete mode 100644 python/trash/untypy/examples/ex03.py delete mode 100644 python/trash/untypy/examples/ex04.py delete mode 100644 python/trash/untypy/examples/ex05.py delete mode 100644 python/trash/untypy/examples/ex06.py delete mode 100644 python/trash/untypy/examples/ex09.py delete mode 100644 python/trash/untypy/examples/ex10.py delete mode 100644 python/trash/untypy/examples/mylogfile delete mode 100644 python/trash/untypy/requirements.txt delete mode 100755 python/trash/untypy/runtests.sh delete mode 100644 python/trash/untypy/setup.py delete mode 100644 python/trash/untypy/test-requirements.txt delete mode 100644 python/trash/untypy/test/__init__.py delete mode 100644 python/trash/untypy/test/impl/__init__.py delete mode 100644 python/trash/untypy/test/impl/test_any.py delete mode 100644 python/trash/untypy/test/impl/test_callable.py delete mode 100644 python/trash/untypy/test/impl/test_dict.py delete mode 100644 python/trash/untypy/test/impl/test_forwardRef.py delete mode 100644 python/trash/untypy/test/impl/test_generator.py delete mode 100644 python/trash/untypy/test/impl/test_interface_dict.py delete mode 100644 python/trash/untypy/test/impl/test_interface_set.py delete mode 100644 python/trash/untypy/test/impl/test_list.py delete mode 100644 python/trash/untypy/test/impl/test_literal.py delete mode 100644 python/trash/untypy/test/impl/test_none.py delete mode 100644 python/trash/untypy/test/impl/test_optional.py delete mode 100644 python/trash/untypy/test/impl/test_protocol.py delete mode 100644 python/trash/untypy/test/impl/test_set.py delete mode 100644 python/trash/untypy/test/impl/test_simple.py delete mode 100644 python/trash/untypy/test/impl/test_str.py delete mode 100644 python/trash/untypy/test/impl/test_tuple.py delete mode 100644 python/trash/untypy/test/impl/test_union.py delete mode 100644 python/trash/untypy/test/patching_dummy/__init__.py delete mode 100644 python/trash/untypy/test/patching_dummy/a/__init__.py delete mode 100644 python/trash/untypy/test/patching_dummy/a/a_sub.py delete mode 100644 python/trash/untypy/test/patching_dummy/argument_types.py delete mode 100644 python/trash/untypy/test/patching_dummy/async_gen.py delete mode 100644 python/trash/untypy/test/patching_dummy/b/__init__.py delete mode 100644 python/trash/untypy/test/patching_dummy/b/b_sub.py delete mode 100644 python/trash/untypy/test/patching_dummy/c.py delete mode 100644 python/trash/untypy/test/patching_dummy/patching_classes.py delete mode 100644 python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py delete mode 100644 python/trash/untypy/test/patching_dummy/unpatched.py delete mode 100644 python/trash/untypy/test/test_patching.py delete mode 100644 python/trash/untypy/test/test_standalone_checker.py delete mode 100644 python/trash/untypy/test/util.py delete mode 100644 python/trash/untypy/test/util_test/__init__.py delete mode 100644 python/trash/untypy/test/util_test/test_return_traces.py delete mode 100644 python/trash/untypy/test/util_test/test_wrapper.py delete mode 100644 python/trash/untypy/test/util_test/untypy_test_case.py delete mode 100644 python/trash/untypy/untypy/__init__.py delete mode 100644 python/trash/untypy/untypy/error.py delete mode 100644 python/trash/untypy/untypy/impl/__init__.py delete mode 100644 python/trash/untypy/untypy/impl/alias.py delete mode 100644 python/trash/untypy/untypy/impl/annotated.py delete mode 100644 python/trash/untypy/untypy/impl/any.py delete mode 100644 python/trash/untypy/untypy/impl/callable.py delete mode 100644 python/trash/untypy/untypy/impl/choice.py delete mode 100644 python/trash/untypy/untypy/impl/dummy_delayed.py delete mode 100644 python/trash/untypy/untypy/impl/generator.py delete mode 100644 python/trash/untypy/untypy/impl/generic.py delete mode 100644 python/trash/untypy/untypy/impl/interface.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/__init__.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/dict.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/iterable.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/list.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/sequence.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/set.py delete mode 100644 python/trash/untypy/untypy/impl/interfaces/util.py delete mode 100644 python/trash/untypy/untypy/impl/literal.py delete mode 100644 python/trash/untypy/untypy/impl/none.py delete mode 100644 python/trash/untypy/untypy/impl/optional.py delete mode 100644 python/trash/untypy/untypy/impl/protocol.py delete mode 100644 python/trash/untypy/untypy/impl/simple.py delete mode 100644 python/trash/untypy/untypy/impl/string_forward_refs.py delete mode 100644 python/trash/untypy/untypy/impl/tuple.py delete mode 100644 python/trash/untypy/untypy/impl/union.py delete mode 100644 python/trash/untypy/untypy/interfaces.py delete mode 100644 python/trash/untypy/untypy/patching/__init__.py delete mode 100644 python/trash/untypy/untypy/patching/ast_transformer.py delete mode 100644 python/trash/untypy/untypy/patching/import_hook.py delete mode 100644 python/trash/untypy/untypy/patching/standalone_checker.py delete mode 100644 python/trash/untypy/untypy/util/__init__.py delete mode 100644 python/trash/untypy/untypy/util/condition.py delete mode 100644 python/trash/untypy/untypy/util/debug.py delete mode 100644 python/trash/untypy/untypy/util/display.py delete mode 100644 python/trash/untypy/untypy/util/return_traces.py delete mode 100644 python/trash/untypy/untypy/util/source_utils.py delete mode 100644 python/trash/untypy/untypy/util/tranformer_combinator.py delete mode 100644 python/trash/untypy/untypy/util/typedfunction.py delete mode 100644 python/trash/untypy/untypy/util/typehints.py delete mode 100644 python/trash/untypy/untypy/util/wrapper.py diff --git a/python/trash/untypy/.gitignore b/python/trash/untypy/.gitignore deleted file mode 100644 index c18dd8d8..00000000 --- a/python/trash/untypy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -__pycache__/ diff --git a/python/trash/untypy/LICENSE b/python/trash/untypy/LICENSE deleted file mode 100644 index 45d7ebab..00000000 --- a/python/trash/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/trash/untypy/README.rst b/python/trash/untypy/README.rst deleted file mode 100644 index 741895cf..00000000 --- a/python/trash/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/trash/untypy/examples/ex01.py b/python/trash/untypy/examples/ex01.py deleted file mode 100644 index ec949b9e..00000000 --- a/python/trash/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/trash/untypy/examples/ex02.py b/python/trash/untypy/examples/ex02.py deleted file mode 100644 index db5fd2ea..00000000 --- a/python/trash/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/trash/untypy/examples/ex03.py b/python/trash/untypy/examples/ex03.py deleted file mode 100644 index 291a12f4..00000000 --- a/python/trash/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/trash/untypy/examples/ex04.py b/python/trash/untypy/examples/ex04.py deleted file mode 100644 index 063eaf86..00000000 --- a/python/trash/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/trash/untypy/examples/ex05.py b/python/trash/untypy/examples/ex05.py deleted file mode 100644 index 4559efed..00000000 --- a/python/trash/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/trash/untypy/examples/ex06.py b/python/trash/untypy/examples/ex06.py deleted file mode 100644 index 7f338277..00000000 --- a/python/trash/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/trash/untypy/examples/ex09.py b/python/trash/untypy/examples/ex09.py deleted file mode 100644 index e5afe101..00000000 --- a/python/trash/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/trash/untypy/examples/ex10.py b/python/trash/untypy/examples/ex10.py deleted file mode 100644 index c16affd6..00000000 --- a/python/trash/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/trash/untypy/examples/mylogfile b/python/trash/untypy/examples/mylogfile deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/requirements.txt b/python/trash/untypy/requirements.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/runtests.sh b/python/trash/untypy/runtests.sh deleted file mode 100755 index ac2b2bb3..00000000 --- a/python/trash/untypy/runtests.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -python3 -m unittest discover "$@" - diff --git a/python/trash/untypy/setup.py b/python/trash/untypy/setup.py deleted file mode 100644 index c4bb0ea0..00000000 --- a/python/trash/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/trash/untypy/test-requirements.txt b/python/trash/untypy/test-requirements.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/test/__init__.py b/python/trash/untypy/test/__init__.py deleted file mode 100644 index 12cfdd65..00000000 --- a/python/trash/untypy/test/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import untypy - -untypy.GlobalConfig = untypy.GlobalConfig._replace(checkedprefixes=["test"]) diff --git a/python/trash/untypy/test/impl/__init__.py b/python/trash/untypy/test/impl/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/test/impl/test_any.py b/python/trash/untypy/test/impl/test_any.py deleted file mode 100644 index 98698a80..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_callable.py b/python/trash/untypy/test/impl/test_callable.py deleted file mode 100644 index bbe772c9..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_dict.py b/python/trash/untypy/test/impl/test_dict.py deleted file mode 100644 index 42851819..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_forwardRef.py b/python/trash/untypy/test/impl/test_forwardRef.py deleted file mode 100644 index 3db02492..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_generator.py b/python/trash/untypy/test/impl/test_generator.py deleted file mode 100644 index 11585561..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_interface_dict.py b/python/trash/untypy/test/impl/test_interface_dict.py deleted file mode 100644 index 540b3af4..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_interface_set.py b/python/trash/untypy/test/impl/test_interface_set.py deleted file mode 100644 index 74ecb9b5..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_list.py b/python/trash/untypy/test/impl/test_list.py deleted file mode 100644 index ec757edc..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_literal.py b/python/trash/untypy/test/impl/test_literal.py deleted file mode 100644 index ded683e9..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_none.py b/python/trash/untypy/test/impl/test_none.py deleted file mode 100644 index 198bbbc5..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_optional.py b/python/trash/untypy/test/impl/test_optional.py deleted file mode 100644 index f64b0e71..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_protocol.py b/python/trash/untypy/test/impl/test_protocol.py deleted file mode 100644 index 2afb8a79..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_set.py b/python/trash/untypy/test/impl/test_set.py deleted file mode 100644 index ae2562d9..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_simple.py b/python/trash/untypy/test/impl/test_simple.py deleted file mode 100644 index 6e70e37d..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_str.py b/python/trash/untypy/test/impl/test_str.py deleted file mode 100644 index a3b28d79..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_tuple.py b/python/trash/untypy/test/impl/test_tuple.py deleted file mode 100644 index a7edb3f9..00000000 --- a/python/trash/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/trash/untypy/test/impl/test_union.py b/python/trash/untypy/test/impl/test_union.py deleted file mode 100644 index 18e68e70..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/__init__.py b/python/trash/untypy/test/patching_dummy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/test/patching_dummy/a/__init__.py b/python/trash/untypy/test/patching_dummy/a/__init__.py deleted file mode 100644 index 369e1ccc..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/a/a_sub.py b/python/trash/untypy/test/patching_dummy/a/a_sub.py deleted file mode 100644 index 2745d71a..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/argument_types.py b/python/trash/untypy/test/patching_dummy/argument_types.py deleted file mode 100644 index da2f8106..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/async_gen.py b/python/trash/untypy/test/patching_dummy/async_gen.py deleted file mode 100644 index 752ce8f0..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/b/__init__.py b/python/trash/untypy/test/patching_dummy/b/__init__.py deleted file mode 100644 index 26da4915..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/b/b_sub.py b/python/trash/untypy/test/patching_dummy/b/b_sub.py deleted file mode 100644 index 2745d71a..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/c.py b/python/trash/untypy/test/patching_dummy/c.py deleted file mode 100644 index 80f56399..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/patching_classes.py b/python/trash/untypy/test/patching_dummy/patching_classes.py deleted file mode 100644 index a088ba5f..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py b/python/trash/untypy/test/patching_dummy/patching_does_not_change_signature.py deleted file mode 100644 index 5ba12547..00000000 --- a/python/trash/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/trash/untypy/test/patching_dummy/unpatched.py b/python/trash/untypy/test/patching_dummy/unpatched.py deleted file mode 100644 index 582d5317..00000000 --- a/python/trash/untypy/test/patching_dummy/unpatched.py +++ /dev/null @@ -1,2 +0,0 @@ -def fn_one(x: int) -> None: - pass diff --git a/python/trash/untypy/test/test_patching.py b/python/trash/untypy/test/test_patching.py deleted file mode 100644 index b1136e2d..00000000 --- a/python/trash/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/trash/untypy/test/test_standalone_checker.py b/python/trash/untypy/test/test_standalone_checker.py deleted file mode 100644 index 66190e8c..00000000 --- a/python/trash/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/trash/untypy/test/util.py b/python/trash/untypy/test/util.py deleted file mode 100644 index 6e20f8f2..00000000 --- a/python/trash/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/trash/untypy/test/util_test/__init__.py b/python/trash/untypy/test/util_test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/test/util_test/test_return_traces.py b/python/trash/untypy/test/util_test/test_return_traces.py deleted file mode 100644 index 5c9e3337..00000000 --- a/python/trash/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/trash/untypy/test/util_test/test_wrapper.py b/python/trash/untypy/test/util_test/test_wrapper.py deleted file mode 100644 index 9d3d921d..00000000 --- a/python/trash/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/trash/untypy/test/util_test/untypy_test_case.py b/python/trash/untypy/test/util_test/untypy_test_case.py deleted file mode 100644 index 52228f37..00000000 --- a/python/trash/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/trash/untypy/untypy/__init__.py b/python/trash/untypy/untypy/__init__.py deleted file mode 100644 index 5f451216..00000000 --- a/python/trash/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/trash/untypy/untypy/error.py b/python/trash/untypy/untypy/error.py deleted file mode 100644 index 0ffeda72..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/__init__.py b/python/trash/untypy/untypy/impl/__init__.py deleted file mode 100644 index d147d692..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/alias.py b/python/trash/untypy/untypy/impl/alias.py deleted file mode 100644 index 559a4c85..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/annotated.py b/python/trash/untypy/untypy/impl/annotated.py deleted file mode 100644 index f9119228..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/any.py b/python/trash/untypy/untypy/impl/any.py deleted file mode 100644 index 62671c8f..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/callable.py b/python/trash/untypy/untypy/impl/callable.py deleted file mode 100644 index 2d092e12..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/choice.py b/python/trash/untypy/untypy/impl/choice.py deleted file mode 100644 index fefd5776..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/dummy_delayed.py b/python/trash/untypy/untypy/impl/dummy_delayed.py deleted file mode 100644 index 81b07704..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/generator.py b/python/trash/untypy/untypy/impl/generator.py deleted file mode 100644 index 6551bdb3..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/generic.py b/python/trash/untypy/untypy/impl/generic.py deleted file mode 100644 index 0f0e65b5..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interface.py b/python/trash/untypy/untypy/impl/interface.py deleted file mode 100644 index 9ecb5957..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/__init__.py b/python/trash/untypy/untypy/impl/interfaces/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/trash/untypy/untypy/impl/interfaces/dict.py b/python/trash/untypy/untypy/impl/interfaces/dict.py deleted file mode 100644 index fdb76902..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/iterable.py b/python/trash/untypy/untypy/impl/interfaces/iterable.py deleted file mode 100644 index b52c7db1..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/list.py b/python/trash/untypy/untypy/impl/interfaces/list.py deleted file mode 100644 index d624d8f3..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/sequence.py b/python/trash/untypy/untypy/impl/interfaces/sequence.py deleted file mode 100644 index 08d58371..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/set.py b/python/trash/untypy/untypy/impl/interfaces/set.py deleted file mode 100644 index 145cb9b1..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/interfaces/util.py b/python/trash/untypy/untypy/impl/interfaces/util.py deleted file mode 100644 index 42e5f170..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/literal.py b/python/trash/untypy/untypy/impl/literal.py deleted file mode 100644 index 03de3e53..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/none.py b/python/trash/untypy/untypy/impl/none.py deleted file mode 100644 index 31180286..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/optional.py b/python/trash/untypy/untypy/impl/optional.py deleted file mode 100644 index b1577c6b..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/protocol.py b/python/trash/untypy/untypy/impl/protocol.py deleted file mode 100644 index e98d0385..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/simple.py b/python/trash/untypy/untypy/impl/simple.py deleted file mode 100644 index 4cb39da7..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/string_forward_refs.py b/python/trash/untypy/untypy/impl/string_forward_refs.py deleted file mode 100644 index 85fa8a21..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/tuple.py b/python/trash/untypy/untypy/impl/tuple.py deleted file mode 100644 index dd5836bc..00000000 --- a/python/trash/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/trash/untypy/untypy/impl/union.py b/python/trash/untypy/untypy/impl/union.py deleted file mode 100644 index b6c973e1..00000000 --- a/python/trash/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/trash/untypy/untypy/interfaces.py b/python/trash/untypy/untypy/interfaces.py deleted file mode 100644 index d6ad5e17..00000000 --- a/python/trash/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/trash/untypy/untypy/patching/__init__.py b/python/trash/untypy/untypy/patching/__init__.py deleted file mode 100644 index 35a1e6c8..00000000 --- a/python/trash/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/trash/untypy/untypy/patching/ast_transformer.py b/python/trash/untypy/untypy/patching/ast_transformer.py deleted file mode 100644 index f48ee49b..00000000 --- a/python/trash/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/trash/untypy/untypy/patching/import_hook.py b/python/trash/untypy/untypy/patching/import_hook.py deleted file mode 100644 index ac44cb02..00000000 --- a/python/trash/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/trash/untypy/untypy/patching/standalone_checker.py b/python/trash/untypy/untypy/patching/standalone_checker.py deleted file mode 100644 index e31428c2..00000000 --- a/python/trash/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/trash/untypy/untypy/util/__init__.py b/python/trash/untypy/untypy/util/__init__.py deleted file mode 100644 index b9e3a845..00000000 --- a/python/trash/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/trash/untypy/untypy/util/condition.py b/python/trash/untypy/untypy/util/condition.py deleted file mode 100644 index 7a053740..00000000 --- a/python/trash/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/trash/untypy/untypy/util/debug.py b/python/trash/untypy/untypy/util/debug.py deleted file mode 100644 index 9f2293be..00000000 --- a/python/trash/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/trash/untypy/untypy/util/display.py b/python/trash/untypy/untypy/util/display.py deleted file mode 100644 index 8e73c940..00000000 --- a/python/trash/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/trash/untypy/untypy/util/return_traces.py b/python/trash/untypy/untypy/util/return_traces.py deleted file mode 100644 index 84eca8b0..00000000 --- a/python/trash/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/trash/untypy/untypy/util/source_utils.py b/python/trash/untypy/untypy/util/source_utils.py deleted file mode 100644 index 6847c21a..00000000 --- a/python/trash/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/trash/untypy/untypy/util/tranformer_combinator.py b/python/trash/untypy/untypy/util/tranformer_combinator.py deleted file mode 100644 index c86de398..00000000 --- a/python/trash/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/trash/untypy/untypy/util/typedfunction.py b/python/trash/untypy/untypy/util/typedfunction.py deleted file mode 100644 index 8c6624a5..00000000 --- a/python/trash/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/trash/untypy/untypy/util/typehints.py b/python/trash/untypy/untypy/util/typehints.py deleted file mode 100644 index 8f9947c7..00000000 --- a/python/trash/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/trash/untypy/untypy/util/wrapper.py b/python/trash/untypy/untypy/util/wrapper.py deleted file mode 100644 index f1a5494f..00000000 --- a/python/trash/untypy/untypy/util/wrapper.py +++ /dev/null @@ -1,292 +0,0 @@ -import typing -import collections -import abc -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) - -_wrapperClasses = {} - -def wrapObj(wrapped, methods, name, extra): - k = wrapped.__class__ - if k in _wrapperClasses: - cls = _wrapperClasses[k] - else: - if isinstance(wrapped, abc.ABC): - class BaseWrapper(WrapperBase, wrapped.__class__, abc.ABC): - def __init__(self, wrapped): - self.__dict__ = wrapped.__dict__ - self.__wrapped__ = wrapped - _wrapperClasses[k] = BaseWrapper - cls = BaseWrapper - else: - class BaseWrapper(WrapperBase, wrapped.__class__): - def __init__(self, wrapped): - self.__dict__ = wrapped.__dict__ - self.__wrapped__ = wrapped - _wrapperClasses[k] = BaseWrapper - cls = BaseWrapper - if name is None: - name = 'ObjectWrapper' - if hasattr(wrapped, '__module__'): - mod = getattr(wrapped, '__module__') - else: - mod = None - return _wrap(wrapped, methods, mod, name, extra, cls) - -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 From fbccc5164b2824581843e9e871846f7d8a6daab2 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sun, 14 Sep 2025 07:14:25 +0200 Subject: [PATCH 21/56] fix unit tests --- python/allTestsForPyVersion | 76 ++++++++++++++++++---------------- python/src/errors.py | 4 ++ python/src/i18n.py | 7 ++++ python/src/writeYourProgram.py | 41 ++++++++++++++---- python/tests/sample.py | 1 - 5 files changed, 85 insertions(+), 44 deletions(-) diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion index 408d2b38..66670afe 100755 --- a/python/allTestsForPyVersion +++ b/python/allTestsForPyVersion @@ -5,11 +5,11 @@ set -u cd $(dirname $0) -unit_test_path=src:tests:deps/untypy +unit_test_path=src:tests:deps function prepare_integration_tests() { - echo "Preparing integration tests by install the WYPP library" + echo "Preparing integration tests by installing the WYPP library" local d=$(mktemp -d) trap "rm -rf $d" EXIT WYPP_INSTALL_DIR=$d python3 src/runYourProgram.py --install-mode installOnly @@ -22,47 +22,53 @@ 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 +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 + PYTHONPATH=$unit_test_path python3 -m unittest "$@" + ecode=$? + fi echo "Done with unit tests" - echo +} + +function run_integration_tests() +{ 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 - else - usage - fi - shift - echo "Running $what tests $@ with PYTHONPATH=$p" if [ -z "${1:-}" ]; then - PYTHONPATH=$p python3 -m unittest $dir/test*.py + PYTHONPATH=$integ_test_path python3 -m unittest integration-tests/test*.py ecode=$? else - PYTHONPATH=$p python3 -m unittest "$@" + PYTHONPATH=$integ_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 ..." -python3 ./fileTests.py diff --git a/python/src/errors.py b/python/src/errors.py index 970d2f9d..bdde1325 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -122,5 +122,9 @@ def partialAnnotationError(callableName: location.CallableName, paramName: str, lines.append(renderLoc(paramLoc)) raise WyppTypeError('\n'.join(lines)) + @staticmethod + def noTypeAnnotationForRecordAttribute(attrName: str, recordName: str) -> WyppTypeError: + return WyppTypeError(i18n.noTypeAnnotationForAttribute(attrName, recordName)) + class WyppAttributeError(AttributeError, WyppError): pass diff --git a/python/src/i18n.py b/python/src/i18n.py index af9f4833..1ffe02f7 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -59,6 +59,9 @@ def tr(key, **kws) -> str: 'Parameter `{param}` der Methode `{fun}` aus Klasse `{cls}` benötigt eine Typangabe', 'Parameter `{param}` of the constructor of class `{cls}` requires a type annotation': 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe', + + 'Attribute `{name}` of record `{record}` required a type annotation': + 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation' } def expectingNoReturn(cn: location.CallableName) -> str: @@ -145,4 +148,8 @@ def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str: 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) + # TODO: write automatic tests to ensure all keys are defined and the all string parameters are defined diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index 5e4106b7..db48ca88 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -1,6 +1,7 @@ import typing import dataclasses import inspect +import sys import myTypeguard import errors import typecheck @@ -42,10 +43,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 @@ -97,6 +96,12 @@ def _collectDataClassAttributes(cls): result = c.__annotations__ | result return result +def _getNamespacesOfClass(cls): + mod = sys.modules.get(cls.__module__) or inspect.getmodule(cls) + globals = vars(mod) if mod else {} + owner = getattr(cls, "__qualname__", "").split(".")[0] + locals = globals.get(owner, {}) + return myTypeguard.Namespaces(globals, locals) def _patchDataClass(cls, mutable: bool): fieldNames = [f.name for f in dataclasses.fields(cls)] @@ -111,20 +116,21 @@ def _patchDataClass(cls, mutable: bool): if mutable: # prevent new fields being added fields = set(fieldNames) + ns = _getNamespacesOfClass(cls) checker = {} - # Note: Partial annotations are disallowed by untypy.typechecked(cls.__init__) - # So no handling in this code is required. + # Note: Partial annotations are disallowed, so no handling in this code is required. for name in fields: if name in cls.__annotations__: - ty = typing.get_type_hints(cls, include_extras=True)[name] def check(v): - if not myTypeguard.matchesTy(v, ty): + ty = typing.get_type_hints(cls, include_extras=True)[name] + if not myTypeguard.matchesTy(v, ty, ns): raise TypeError(f'Expected argument of type {myTypeguard.renderTy(ty)} ' \ f'for attribute {name}, got {myTypeguard.renderTy(type(v))}: {v}') + return v checker[name] = check else: - raise errors.WyppTypeError(f'No type annotation for attribute {name}') + raise errors.WyppTypeError.noTypeAnnotationForRecordAttribute(name, cls.__name__) oldSetattr = cls.__setattr__ def _setattr(obj, k, v): @@ -139,7 +145,7 @@ def _setattr(obj, k, v): return cls @typing.dataclass_transform() -def record(cls, mutable=False): +def record(cls=None, mutable=False): def wrap(cls: type): newCls = dataclasses.dataclass(cls, frozen=not mutable) if _typeCheckingEnabled: @@ -154,6 +160,25 @@ def wrap(cls: type): # We're called as @dataclass without parens. return wrap(cls) +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: + cfg = {'kind': 'function'} + else: + 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(func) + # Tests _die = False diff --git a/python/tests/sample.py b/python/tests/sample.py index a4e24810..975593c7 100644 --- a/python/tests/sample.py +++ b/python/tests/sample.py @@ -1,6 +1,5 @@ from writeYourProgram import * import math -from untypy import typechecked import typing Drink = Literal["Tea", "Coffee"] From fec1bd631545241ca251e051be083ffc79e681f4 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Sun, 14 Sep 2025 07:39:03 +0200 Subject: [PATCH 22/56] test for I18N --- python/src/i18n.py | 56 +++++++++------ .../tests/{testDeepEq.py => test_deepEq.py} | 0 .../tests/{testDrawing.py => test_drawing.py} | 0 python/tests/test_i18n.py | 68 +++++++++++++++++++ python/tests/{testMisc.py => test_misc.py} | 0 .../tests/{testRecord.py => test_record.py} | 0 .../tests/{testSample.py => test_sample.py} | 0 7 files changed, 104 insertions(+), 20 deletions(-) rename python/tests/{testDeepEq.py => test_deepEq.py} (100%) rename python/tests/{testDrawing.py => test_drawing.py} (100%) create mode 100644 python/tests/test_i18n.py rename python/tests/{testMisc.py => test_misc.py} (100%) rename python/tests/{testRecord.py => test_record.py} (100%) rename python/tests/{testSample.py => test_sample.py} (100%) diff --git a/python/src/i18n.py b/python/src/i18n.py index 1ffe02f7..8838595f 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -1,14 +1,30 @@ from dataclasses import dataclass import location from typing import * +from contextlib import contextmanager type Lang = Literal['en', 'de'] -def lang() -> Lang: - return 'de' +allLanguages: list[Lang] = ['en', 'de'] + +_lang: Lang = 'de' + +@contextmanager +def lang(newLang: Lang): + """Context manager to temporarily set the language.""" + global _lang + oldLang = _lang + _lang = newLang + try: + yield + finally: + _lang = oldLang + +def getLang() -> Lang: + return _lang def tr(key, **kws) -> str: - match lang(): + match getLang(): case 'en': tmpl = key case 'de': @@ -53,15 +69,15 @@ def tr(key, **kws) -> str: 'Problematic call in line': 'Fehlerhafter Aufruf in Zeile', 'Call causing the problematic return in line': 'Aufruf, der das fehlerhafte return verursacht, in Zeile', - 'Parameter `{param}` of function `{fun}` requires a type annotation': - 'Parameter `{param}` der Funktion `{fun}` benötigt eine Typangabe', - 'Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation': - 'Parameter `{param}` der Methode `{fun}` aus Klasse `{cls}` benötigt eine Typangabe', - 'Parameter `{param}` of the constructor of class `{cls}` requires a type annotation': - 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe', + 'Parameter `{param}` of function `{fun}` requires a type annotation.': + 'Parameter `{param}` der Funktion `{fun}` benötigt eine Typangabe.', + 'Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation.': + 'Parameter `{param}` der Methode `{method}` aus Klasse `{cls}` benötigt eine Typangabe.', + 'Parameter `{param}` of the constructor of class `{cls}` requires a type annotation.': + 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe.', - 'Attribute `{name}` of record `{record}` required a type annotation': - 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation' + 'Attribute `{name}` of record `{record}` required a type annotation.': + 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation.' } def expectingNoReturn(cn: location.CallableName) -> str: @@ -83,13 +99,13 @@ def wrongReturnValue(ty: str) -> str: 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}`', + 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}`', + 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('constructor', cls): - return tr('Expecting return value of type `{ty}` when calling constructor of class `{cls}`', + return tr('Expecting return value of type `{ty}` when calling constructor of class `{cls}`.', cls=cls, ty=ty) raise ValueError(f'Unexpected: {cn}') @@ -97,7 +113,7 @@ def noReturnValue() -> str: return tr('But no return found.') def transArg(pos: int): - match lang(): + match getLang(): case 'en': match pos: case 1: '1st argument' @@ -128,7 +144,7 @@ def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int) -> str: 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('constructor', cls): - return tr('The call of method `{method}` of class `{cls}` expects value of type `{ty}` as {arg}.', + return tr('The call of the constructor of class `{cls}` expects value of type `{ty}` as {arg}.', cls=cls, ty=ty, arg=arg) raise ValueError(f'Unexpected: {cn}') @@ -138,18 +154,18 @@ def realArgumentTy(ty: str) -> str: def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str: match cn.kind: case 'function': - return tr('Parameter `{param}` of function `{fun}` requires a type annotation', + 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', + return tr('Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation.', method=cn.name, cls=cls, param=param) case location.ClassMember('constructor', cls): - return tr('Parameter `{param}` of the constructor of class `{cls}` requires a type annotation', + return tr('Parameter `{param}` of the constructor of class `{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', + return tr('Attribute `{name}` of record `{record}` required a type annotation.', name=attrName, record=recordName) # TODO: write automatic tests to ensure all keys are defined and the all string parameters are defined diff --git a/python/tests/testDeepEq.py b/python/tests/test_deepEq.py similarity index 100% rename from python/tests/testDeepEq.py rename to python/tests/test_deepEq.py diff --git a/python/tests/testDrawing.py b/python/tests/test_drawing.py similarity index 100% rename from python/tests/testDrawing.py rename to python/tests/test_drawing.py diff --git a/python/tests/test_i18n.py b/python/tests/test_i18n.py new file mode 100644 index 00000000..97705df3 --- /dev/null +++ b/python/tests/test_i18n.py @@ -0,0 +1,68 @@ +import unittest +import i18n +import location +import inspect + +class TestI18nTranslations(unittest.TestCase): + def setUp(self): + # Get all functions that return strings + blacklist = ['tr'] + self.stringFunctions = [] + for name, func in inspect.getmembers(i18n, inspect.isfunction): + if not name in blacklist and \ + hasattr(func, '__annotations__') and func.__annotations__.get('return') == str: + self.stringFunctions.append((name, func)) + + def _getCallableNameVariants(self): + return [ + location.CallableName('foo', 'function'), + location.CallableName('foo', location.ClassMember('method', 'foo')), + location.CallableName('foo', location.ClassMember('constructor', 'foo')) + ] + + def _getTestArgs(self, func): + sig = inspect.signature(func) + args = [] + hasCallableName = False + + for paramName, param in sig.parameters.items(): + if param.annotation == str: + args.append('foo') + elif param.annotation == int: + args.append(1) + elif param.annotation == location.CallableName: + args.append(None) # Placeholder + hasCallableName = True + else: + args.append('foo') # Default fallback + + if hasCallableName: + # Generate variants for each CallableName parameter + variants = [] + for variant in self._getCallableNameVariants(): + testArgs = [] + for i, arg in enumerate(args): + if arg is None: # CallableName placeholder + testArgs.append(variant) + else: + testArgs.append(arg) + variants.append(testArgs) + return variants + else: + return [args] if args else [[]] + + def testAllI18NFunctions(self): + for lang in i18n.allLanguages: + with i18n.lang(lang): + with self.subTest(lang=lang): + for funcName, func in self.stringFunctions: + with self.subTest(function=funcName): + argVariants = self._getTestArgs(func) + for args in argVariants: + with self.subTest(args=args): + result = func(*args) + self.assertIsInstance(result, str) + self.assertGreater(len(result), 0) + +if __name__ == '__main__': + unittest.main() diff --git a/python/tests/testMisc.py b/python/tests/test_misc.py similarity index 100% rename from python/tests/testMisc.py rename to python/tests/test_misc.py diff --git a/python/tests/testRecord.py b/python/tests/test_record.py similarity index 100% rename from python/tests/testRecord.py rename to python/tests/test_record.py diff --git a/python/tests/testSample.py b/python/tests/test_sample.py similarity index 100% rename from python/tests/testSample.py rename to python/tests/test_sample.py From 312cfffa9f2d77948c5b1945942f1345c89c9b20 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 15 Sep 2025 12:44:06 +0200 Subject: [PATCH 23/56] get language from environment --- python/src/i18n.py | 13 ++++++++----- python/tests/test_i18n.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/python/src/i18n.py b/python/src/i18n.py index 8838595f..30f94836 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -2,15 +2,17 @@ import location from typing import * from contextlib import contextmanager +import lang type Lang = Literal['en', 'de'] allLanguages: list[Lang] = ['en', 'de'] -_lang: Lang = 'de' +# If not set explicitly, the current locale is asked for the language +_lang: Optional[Lang] = None @contextmanager -def lang(newLang: Lang): +def explicitLang(newLang: Lang): """Context manager to temporarily set the language.""" global _lang oldLang = _lang @@ -21,7 +23,10 @@ def lang(newLang: Lang): _lang = oldLang def getLang() -> Lang: - return _lang + if _lang: + return _lang + else: + return lang.pickLanguage(allLanguages, 'en') def tr(key, **kws) -> str: match getLang(): @@ -167,5 +172,3 @@ def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str: def noTypeAnnotationForAttribute(attrName: str, recordName: str) -> str: return tr('Attribute `{name}` of record `{record}` required a type annotation.', name=attrName, record=recordName) - -# TODO: write automatic tests to ensure all keys are defined and the all string parameters are defined diff --git a/python/tests/test_i18n.py b/python/tests/test_i18n.py index 97705df3..085bf0bf 100644 --- a/python/tests/test_i18n.py +++ b/python/tests/test_i18n.py @@ -53,7 +53,7 @@ def _getTestArgs(self, func): def testAllI18NFunctions(self): for lang in i18n.allLanguages: - with i18n.lang(lang): + with i18n.explicitLang(lang): with self.subTest(lang=lang): for funcName, func in self.stringFunctions: with self.subTest(function=funcName): From 4523802f4e34810122e80dbcc758006825cdf989 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 16:20:50 +0200 Subject: [PATCH 24/56] more unit tests --- python/TODO_nowrappers.md | 5 -- python/allTestsForPyVersion | 8 +-- python/src/errors.py | 20 +++--- python/src/location.py | 97 +++++++++++--------------- python/src/stacktrace.py | 3 - python/test-data-2.0/functionArg.py | 3 + python/test-data-2.0/functionResult.py | 3 + 7 files changed, 64 insertions(+), 75 deletions(-) diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 61391c6b..de6a0bb5 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,8 +1,3 @@ -* German translation of error messages -* Unit tests: - * FunctionCode class - * locationOfArgument - * ReturnTracker * Fix unit tests in test-data * Typechecked console * Debug slow startup times diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion index 66670afe..8e19b2fb 100755 --- a/python/allTestsForPyVersion +++ b/python/allTestsForPyVersion @@ -24,9 +24,9 @@ function usage() function run_unit_tests() { - echo "Running unit tests, PYTHONPATH=$unit_test_path" + echo "Running unit tests, PYTHONPATH=$unit_test_path" if [ -z "${1:-}" ]; then - PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py + PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py ecode=$? else PYTHONPATH=$unit_test_path python3 -m unittest "$@" @@ -62,11 +62,11 @@ if [ -z "${1:-}" ]; then python3 ./fileTests.py elif [ "$1" == "--unit" ]; then shift - run_unit_tests + run_unit_tests "$@" exit $ecode elif [ "$1" == "--integration" ]; then shift - run_integration_tests + run_integration_tests "$@" exit $ecode else usage diff --git a/python/src/errors.py b/python/src/errors.py index bdde1325..6a056299 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -70,19 +70,23 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy))) if not isParameterizedType(resultTy): lines.append(i18n.wrongReturnValue(renderTy(givenValue))) - if resultTypeLoc and callLoc: + printedFileName = None + if 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(renderLoc(resultTypeLoc)) + if returnLoc: lines.append('') - if returnLoc: - if resultTypeLoc.filename != returnLoc.filename: - lines.append(f'## {i18n.tr("File")} {returnLoc.filename}') - lines.append(f'## {i18n.tr("Problematic return in line")} {returnLoc.startLine}:\n') - lines.append(renderLoc(returnLoc)) - lines.append('') - if resultTypeLoc.filename != callLoc.filename: + 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(renderLoc(returnLoc)) + if callLoc: + lines.append('') + if printedFileName != callLoc.filename: lines.append(f'## {i18n.tr("File")} {callLoc.filename}') lines.append(f'## {i18n.tr("Call causing the problematic return in line")} {callLoc.startLine}:\n') lines.append(renderLoc(callLoc)) diff --git a/python/src/location.py b/python/src/location.py index 1378f52a..07287386 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -9,8 +9,9 @@ import ansi import utils import myLogging -import re +import sys import abc +import parsecache @dataclass class Loc: @@ -137,7 +138,6 @@ def getResultTypeLocation(self) -> Optional[Loc]: def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: pass -# FIXME: write unit tests class StdCallableInfo(CallableInfo): """ Class giving access to various properties of a function @@ -145,30 +145,16 @@ class StdCallableInfo(CallableInfo): """ def __init__(self, f: Callable, kind: CallableKind): super().__init__(kind) - try: - self.source = inspect.getsource(f) - except Exception: - self.source = None - if self.source: - try: - self.tree = ast.parse(self.source) - except Exception: - self.tree = None - else: - self.tree = None - self._name = f.__name__ self.file = f.__code__.co_filename + self.__name = f.__name__ + self.__ast = parsecache.getAST(self.file) @property def name(self): - return self._name + return self.__name def _findDef(self) -> Optional[ast.FunctionDef | ast.AsyncFunctionDef]: - if not self.tree: - return None - for node in ast.walk(self.tree): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == self.name: - return node + return self.__ast.getFunDef(self.__name) def getResultTypeLocation(self) -> Optional[Loc]: """ @@ -177,23 +163,15 @@ def getResultTypeLocation(self) -> Optional[Loc]: node = self._findDef() if not node: return None - assert self.source is not None r = node.returns if r: - return Loc(self.file, r.lineno, r.col_offset, r.end_lineno, r.end_col_offset) + return Loc(self.file, + r.lineno, + r.col_offset, + r.end_lineno, + r.end_col_offset) else: - funNameRe = re.compile(r'def\s+([^\s()]+)') - lineNo = node.lineno - 1 - colNo = node.col_offset - lines = self.source.split('\n') - if lineNo < len(lines): - line = lines[lineNo][colNo:] - m = funNameRe.match(line) - if m: - name = m.group(1) - i = line.find(name) - nameCol = colNo + i - return Loc(self.file, node.lineno, nameCol, node.lineno, nameCol + len(name)) + # There is no return type annotation return None def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: @@ -217,41 +195,50 @@ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: if res is None: return None else: - return Loc(self.file, res.lineno, res.col_offset, res.end_lineno, res.end_col_offset) + 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 -# FIXME: write unit tests class RecordConstructorInfo(CallableInfo): """ Class giving access to various properties of a record constructor. """ def __init__(self, cls: type): super().__init__(ClassMember('constructor', cls.__name__)) - self.cls = cls - try: - self.source = inspect.getsource(cls) - except Exception: - self.source = None - if self.source: - self.tree = ast.parse(self.source) - else: - self.tree = None + self.__cls = cls @property def name(self): - return self.cls.__name__ + return self.__cls.__name__ def getResultTypeLocation(self) -> Optional[Loc]: return None def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: - if not self.tree: + 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 - for node in ast.walk(self.tree): - print(node) - if isinstance(node, ast.AnnAssign): - if isinstance(node.target, ast.Name): - file = self.cls.__code__.co_filename - return Loc(file, node.lineno, node.col_offset, node.end_lineno, node.end_col_offset) - -# FIXME: write unit test def locationOfArgument(fi: inspect.FrameInfo, idx: int) -> Optional[Loc]: """ Given a stack frame with a function call f(arg1, arg2, ..., argN), returns diff --git a/python/src/stacktrace.py b/python/src/stacktrace.py index c913e814..e862d7d3 100644 --- a/python/src/stacktrace.py +++ b/python/src/stacktrace.py @@ -46,15 +46,12 @@ def limitTraceback(frameList: list[types.FrameType], def callerOutsideWypp() -> Optional[inspect.FrameInfo]: stack = inspect.stack() - #for fi in stack: - # print(f'{fi.filename}:{fi.lineno}') 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 -# FIXME: unit tests class ReturnTracker: def __init__(self): self.__returnFrame: Optional[types.FrameType] = None diff --git a/python/test-data-2.0/functionArg.py b/python/test-data-2.0/functionArg.py index 9fcf0bd2..fa3619c6 100644 --- a/python/test-data-2.0/functionArg.py +++ b/python/test-data-2.0/functionArg.py @@ -1,3 +1,6 @@ +def bar(s: str) -> str: + return s + def foo(i: int, j: int) -> int: return i + 1 diff --git a/python/test-data-2.0/functionResult.py b/python/test-data-2.0/functionResult.py index 5f299e99..52439efb 100644 --- a/python/test-data-2.0/functionResult.py +++ b/python/test-data-2.0/functionResult.py @@ -1,3 +1,6 @@ +def bar(s: str) -> int: + return len(s) + def foo(i: int) -> int: return "foo_" + str(i) From 990ce4136b4c70144a5a87a8942a839bf8f3cacd Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 17:09:32 +0200 Subject: [PATCH 25/56] functionality for recording expected test results --- python/fileTests-2.0.py | 4 +- python/fileTestsLib.py | 119 +++++++++++++++++++++++++++++++--------- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index 7e9abe5d..722090e2 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -5,6 +5,8 @@ for file in directory.iterdir(): if file.is_file(): - checkBasic(file.as_posix()) + name = file.as_posix() + if name.endswith('.py'): + checkBasic(name) globalCtx.results.finish() diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 5a0c463d..a0e75945 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -6,6 +6,7 @@ import tempfile import argparse import re +import shutil @dataclass(frozen=True) class TestOpts: @@ -14,6 +15,7 @@ class TestOpts: startAt: Optional[str] only: Optional[str] keepGoing: bool + record: Optional[str] def parseArgs() -> TestOpts: parser = argparse.ArgumentParser( @@ -27,6 +29,8 @@ def parseArgs() -> TestOpts: 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.') # Parse the arguments args = parser.parse_args() @@ -37,7 +41,8 @@ def parseArgs() -> TestOpts: baseDir=scriptDir, startAt=args.start_at, only=args.only, - keepGoing=args.keepGoing + keepGoing=args.keepGoing, + record=args.record ) TestStatus = Literal['passed', 'failed', 'skipped'] @@ -179,6 +184,47 @@ def fixOutput(filePath: str): 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, + 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('de') + cmd.extend(args) + env = os.environ.copy() + env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) + 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 _check(testFile: str, exitCode: int, typecheck: bool, @@ -196,33 +242,11 @@ def _check(testFile: str, expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) - # Prepare the command - cmd = [sys.executable, ctx.opts.cmd, '--quiet'] - if not typecheck: - cmd.append('--no-typechecking') - cmd.append(testFile) - cmd.extend(args) - with tempfile.TemporaryDirectory() as d: actualStdoutFile = os.path.join(d, 'stdout.txt') actualStderrFile = os.path.join(d, 'stderr.txt') - env = os.environ.copy() - env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) - 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' + _runTest(testFile, exitCode, typecheck, args, actualStdoutFile, actualStderrFile, + pythonPath, what, ctx) fixOutput(actualStdoutFile) fixOutput(actualStderrFile) @@ -267,3 +291,48 @@ def checkBasic(testFile: str, ctx: TestContext = globalCtx): else: expectedExitCode = 1 check(testFile, exitCode=expectedExitCode, 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 = 0 if testFile.endswith('_ok.py') else 1 + typecheck = True + args = [] + pythonPath = [] + 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) + 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) + expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) + shutil.copy(actualStdoutFile, expectedStdoutFile) + shutil.copy(actualStderrFile, expectedStderrFile) + print(f'Stored expected output in {expectedStdoutFile} and {expectedStderrFile}') + else: + print('Aborting') + +if globalCtx.opts.record is not None: + record(globalCtx.opts.record) + sys.exit(0) From ffb6fe510f20e3822f10d48dc80e69bdb937ce88 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 17:09:57 +0200 Subject: [PATCH 26/56] cmdline option for language --- python/src/i18n.py | 4 ++++ python/src/runner.py | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/python/src/i18n.py b/python/src/i18n.py index 30f94836..fa342343 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -28,6 +28,10 @@ def getLang() -> Lang: else: return lang.pickLanguage(allLanguages, 'en') +def setLang(lang: Lang): + global _lang + _lang = lang + def tr(key, **kws) -> str: match getLang(): case 'en': diff --git a/python/src/runner.py b/python/src/runner.py index c0f21535..9927c2d7 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -15,9 +15,10 @@ import runpy import types from dataclasses import dataclass -import stacktrace # local imports +import stacktrace +import i18n import typecheck import instrument from myLogging import * @@ -77,6 +78,8 @@ def parseCmdlineArgs(argList): 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', @@ -463,6 +466,13 @@ def main(globals, argList=None): verbose(f'VERBOSE={VERBOSE}, DEBUG={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) + installLib(args.installMode) if args.installMode == InstallMode.dontInstall: if SITELIB_DIR not in sys.path: From d82f3b258c8ebd76ff3b1560936ce08fa03ff991 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 17:10:09 +0200 Subject: [PATCH 27/56] bugfix --- python/src/i18n.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/python/src/i18n.py b/python/src/i18n.py index fa342343..168dc633 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -125,22 +125,24 @@ def transArg(pos: int): match getLang(): case 'en': match pos: - case 1: '1st argument' - case 2: '2nd argument' - case 3: '3rd argument' - case _: f'{pos}th argument' + 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: 'erstes Argument' - case 2: 'zweites Argument' - case 3: 'drittes Argument' - case 4: 'viertes Argument' - case 5: 'fünftes Argument' - case 6: 'sechstes Argument' - case 7: 'siebtes Argument' - case 8: 'achtes Argument' - case 9: 'neuntes Argument' - case _: f'{pos}. Argument' + 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: From ae8a25bca0a09bd4d53b9f7e3798b65c36335e4c Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 17:34:51 +0200 Subject: [PATCH 28/56] add missing files --- python/test-data-2.0/functionArg.err | 17 +++++++ python/test-data-2.0/functionArg.err_en | 17 +++++++ python/test-data-2.0/functionArg.out | 0 python/test-data-2.0/functionArg.out_en | 0 python/test-data-2.0/functionResult2.py | 7 +++ python/tests/locationTestData.py | 29 +++++++++++ python/tests/stacktraceTestData.py | 13 +++++ python/tests/test_location.py | 58 ++++++++++++++++++++++ python/tests/test_parsecache.py | 65 +++++++++++++++++++++++++ python/tests/test_stacktrace.py | 37 ++++++++++++++ 10 files changed, 243 insertions(+) create mode 100644 python/test-data-2.0/functionArg.err create mode 100644 python/test-data-2.0/functionArg.err_en create mode 100644 python/test-data-2.0/functionArg.out create mode 100644 python/test-data-2.0/functionArg.out_en create mode 100644 python/test-data-2.0/functionResult2.py create mode 100644 python/tests/locationTestData.py create mode 100644 python/tests/stacktraceTestData.py create mode 100644 python/tests/test_location.py create mode 100644 python/tests/test_parsecache.py create mode 100644 python/tests/test_stacktrace.py diff --git a/python/test-data-2.0/functionArg.err b/python/test-data-2.0/functionArg.err new file mode 100644 index 00000000..a5144416 --- /dev/null +++ b/python/test-data-2.0/functionArg.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 /Users/swehr/devel/write-your-python-program/python/test-data-2.0/functionArg.py +## Fehlerhafter Aufruf in Zeile 8: + + print(foo(foo(1, "1"), 42)) + +## Typ deklariert in Zeile 4: + +def foo(i: int, j: int) -> int: diff --git a/python/test-data-2.0/functionArg.err_en b/python/test-data-2.0/functionArg.err_en new file mode 100644 index 00000000..57ef31d2 --- /dev/null +++ b/python/test-data-2.0/functionArg.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 /Users/swehr/devel/write-your-python-program/python/test-data-2.0/functionArg.py +## Problematic call in line 8: + + print(foo(foo(1, "1"), 42)) + +## Type declared in line 4: + +def foo(i: int, j: int) -> int: diff --git a/python/test-data-2.0/functionArg.out b/python/test-data-2.0/functionArg.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionArg.out_en b/python/test-data-2.0/functionArg.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult2.py b/python/test-data-2.0/functionResult2.py new file mode 100644 index 00000000..fd81e2bf --- /dev/null +++ b/python/test-data-2.0/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/tests/locationTestData.py b/python/tests/locationTestData.py new file mode 100644 index 00000000..4ca50ece --- /dev/null +++ b/python/tests/locationTestData.py @@ -0,0 +1,29 @@ +import inspect +# no other imports here (put them below) to keep the line number for myFun stable + +lineNoMyFunDef = 12 +lineNoMyFunCall = lineNoMyFunDef + 2 +lineNoMyFunNoResultDef = 19 +lineNoTestRecordDef = 26 + +def blub(i: int) -> int: + return i + 1 + +def myFun(a: str, b: list[int], depth: int=0) -> inspect.FrameInfo: + if depth == 0: + return myFun("blub", [42, 0], 1) # that's the call + else: + stack = inspect.stack() + return stack[1] + +def myFunNoResult(depth: int=0): + if depth == 0: + return myFunNoResult(1) + else: + stack = inspect.stack() + return stack[1] + +class TestRecord: + x: int + y: str + z: float = 3.14 diff --git a/python/tests/stacktraceTestData.py b/python/tests/stacktraceTestData.py new file mode 100644 index 00000000..7d083dc4 --- /dev/null +++ b/python/tests/stacktraceTestData.py @@ -0,0 +1,13 @@ +f1ReturnLine = 6 +f2ReturnLine = 9 +f3ReturnLine = 13 + +def f1(): + return 42 + +def f2(): + f1() + +def f3(): + f1() + return 0 diff --git a/python/tests/test_location.py b/python/tests/test_location.py new file mode 100644 index 00000000..a7fa5597 --- /dev/null +++ b/python/tests/test_location.py @@ -0,0 +1,58 @@ +import unittest +import os +import location +from locationTestData import * + +class TestLocation(unittest.TestCase): + + def assertLocation(self, + loc: location.Loc | None, + startLine: int, startCol: int, endLine: int, endCol: int): + if loc is None: + self.fail("loc is None") + self.assertEqual('locationTestData.py', os.path.basename(loc.filename)) + self.assertEqual(startLine, loc.startLine) + self.assertEqual(endLine, loc.endLine) + self.assertEqual(startCol, loc.startCol) + self.assertEqual(endCol, loc.endCol) + + def test_locationOfArgument(self): + fi = myFun("foo", [1,2,3]) + loc = location.locationOfArgument(fi, 0) + colNo = 21 + self.assertLocation(loc, lineNoMyFunCall, + colNo, + lineNoMyFunCall, + colNo + len("blub") + 2) + + def test_StdCallableInfo(self): + info = location.StdCallableInfo(myFun, 'function') + self.assertEqual('locationTestData.py', os.path.basename(info.file)) + resultLoc = info.getResultTypeLocation() + argLoc = info.getParamSourceLocation("depth") + self.assertLocation(resultLoc, lineNoMyFunDef, 49, lineNoMyFunDef, 66) + self.assertLocation(argLoc, lineNoMyFunDef, 32, lineNoMyFunDef, 42) + + def test_StdCallableInfo2(self): + info = location.StdCallableInfo(myFunNoResult, 'function') + self.assertEqual('locationTestData.py', os.path.basename(info.file)) + resultLoc = info.getResultTypeLocation() + self.assertIsNone(resultLoc) + argLoc = info.getParamSourceLocation("depth") + self.assertLocation(argLoc, lineNoMyFunNoResultDef, 18, lineNoMyFunNoResultDef, 28) + + def test_RecordConstructorInfo(self): + info = location.RecordConstructorInfo(TestRecord) + # Test getting parameter source locations with precise line/column numbers + argLocX = info.getParamSourceLocation("x") + argLocY = info.getParamSourceLocation("y") + argLocZ = info.getParamSourceLocation("z") + self.assertLocation(argLocX, lineNoTestRecordDef + 1, 4, lineNoTestRecordDef + 1, 10) + self.assertLocation(argLocY, lineNoTestRecordDef + 2, 4, lineNoTestRecordDef + 2, 10) + self.assertLocation(argLocZ, lineNoTestRecordDef + 3, 4, lineNoTestRecordDef + 3, 19) + # Test non-existent parameter + nonExistentLoc = info.getParamSourceLocation("nonexistent") + self.assertIsNone(nonExistentLoc) + # Test result type location (should be None for constructors) + resultLoc = info.getResultTypeLocation() + self.assertIsNone(resultLoc) diff --git a/python/tests/test_parsecache.py b/python/tests/test_parsecache.py new file mode 100644 index 00000000..afab32db --- /dev/null +++ b/python/tests/test_parsecache.py @@ -0,0 +1,65 @@ +# Test data + +def bar(): + pass + +def foo(): + pass + +class C: + x: int + y: str + +class D: + x: int + z: str + +import unittest +import parsecache + +class TestParseCache(unittest.TestCase): + + def test_parseCacheFunDef(self): + a = parsecache.getAST('tests/test_parsecache.py') + assert(a is not None) + defFoo = a.getFunDef('foo') + assert(defFoo is not None) + self.assertEqual(defFoo.lineno, 6) + defBar = a.getFunDef('bar') + assert(defBar is not None) + self.assertEqual(defBar.lineno, 3) + x = a.getFunDef('spam') + self.assertIsNone(x) + + def test_parseCacheRecordAttr(self): + a = parsecache.getAST('tests/test_parsecache.py') + assert(a is not None) + + # Test getting attributes from class C + attrX_C = a.getRecordAttr('C', 'x') + assert(attrX_C is not None) + self.assertEqual(attrX_C.lineno, 10) + + attrY_C = a.getRecordAttr('C', 'y') + assert(attrY_C is not None) + self.assertEqual(attrY_C.lineno, 11) + + # Test getting attributes from class D + attrX_D = a.getRecordAttr('D', 'x') + assert(attrX_D is not None) + self.assertEqual(attrX_D.lineno, 14) + + attrZ_D = a.getRecordAttr('D', 'z') + assert(attrZ_D is not None) + self.assertEqual(attrZ_D.lineno, 15) + + # Test non-existent attribute + nonExistent = a.getRecordAttr('C', 'z') + self.assertIsNone(nonExistent) + + # Test non-existent class + nonExistentClass = a.getRecordAttr('NonExistentClass', 'x') + self.assertIsNone(nonExistentClass) + + + diff --git a/python/tests/test_stacktrace.py b/python/tests/test_stacktrace.py new file mode 100644 index 00000000..c35c3d0f --- /dev/null +++ b/python/tests/test_stacktrace.py @@ -0,0 +1,37 @@ +import unittest +import sys +import types +import stacktrace +from stacktraceTestData import * +import os + +class TestReturnTracker(unittest.TestCase): + + def setUp(self): + # Save original profile function + self.original_profile = sys.getprofile() + + def tearDown(self): + # Restore original profile function + sys.setprofile(self.original_profile) + + def assertReturnFrame(self, tracker: stacktrace.ReturnTracker, line: int): + frame = tracker.getReturnFrame() + assert(frame is not None) + self.assertEqual(os.path.basename(frame.filename), 'stacktraceTestData.py') + self.assertEqual(frame.lineno, line) + + def test_returnTracker1(self): + tracker = stacktrace.installProfileHook() + f1() + self.assertReturnFrame(tracker, f1ReturnLine) + + def test_returnTracker2(self): + tracker = stacktrace.installProfileHook() + f2() + self.assertReturnFrame(tracker, f2ReturnLine) + + def test_returnTracker3(self): + tracker = stacktrace.installProfileHook() + f3() + self.assertReturnFrame(tracker, f3ReturnLine) From c6a19fc123f3771a3efc8ce9ab3f1e67462d90d2 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 18 Sep 2025 17:35:00 +0200 Subject: [PATCH 29/56] support for lang in tests --- python/fileTestsLib.py | 74 +++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index a0e75945..dcb3658a 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -16,6 +16,7 @@ class TestOpts: only: Optional[str] keepGoing: bool record: Optional[str] + lang: str def parseArgs() -> TestOpts: parser = argparse.ArgumentParser( @@ -31,6 +32,8 @@ def parseArgs() -> TestOpts: 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() @@ -42,9 +45,12 @@ def parseArgs() -> TestOpts: startAt=args.start_at, only=args.only, keepGoing=args.keepGoing, - record=args.record + record=args.record, + lang=args.lang ) +defaultLang = 'de' + TestStatus = Literal['passed', 'failed', 'skipped'] @dataclass @@ -96,7 +102,9 @@ def readFileIfExists(filePath: str) -> str: else: return readFile(filePath) -def getVersionedFile(base: str, typcheck: bool) -> str: +def getVersionedFile(base: str, typcheck: bool, lang: str) -> str: + 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: @@ -199,6 +207,7 @@ def _runTest(testFile: 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'] @@ -206,7 +215,7 @@ def _runTest(testFile: str, cmd.append('--no-typechecking') cmd.append(testFile) cmd.append('--lang') - cmd.append('de') + cmd.append(lang) cmd.extend(args) env = os.environ.copy() env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) @@ -225,28 +234,25 @@ def _runTest(testFile: str, print(f"Test {testFile}{what} failed: Expected exit code {exitCode}, got {result.returncode}") return 'failed' -def _check(testFile: str, +def _checkForLang(testFile: str, exitCode: int, typecheck: bool, args: list[str], pythonPath: list[str], - minVersion: Optional[tuple[int, int]], checkOutputs: bool, + lang: str, ctx: TestContext, what: str) -> TestStatus: - if shouldSkip(testFile, ctx, minVersion): - return 'skipped' - # Prepare expected output files baseFile = os.path.splitext(testFile)[0] - expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) - expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) + 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') _runTest(testFile, exitCode, typecheck, args, actualStdoutFile, actualStderrFile, - pythonPath, what, ctx) + pythonPath, what, lang, ctx) fixOutput(actualStdoutFile) fixOutput(actualStderrFile) @@ -258,10 +264,39 @@ def _check(testFile: str, if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): return 'failed' - # If all checks pass - print(f"{testFile}{what} OK") + # 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: + if shouldSkip(testFile, ctx, minVersion): + return 'skipped' + 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 check(testFile: str, exitCode: int = 1, typecheck: bool = True, @@ -315,7 +350,7 @@ def display(filename: str, where: str): 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) + 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) @@ -325,14 +360,15 @@ def display(filename: str, where: str): if answer: fixOutput(actualStdoutFile) fixOutput(actualStderrFile) - expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck) - expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck) + 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 globalCtx.opts.record is not None: - record(globalCtx.opts.record) - sys.exit(0) +if __name__ == '__main__': + if globalCtx.opts.record is not None: + record(globalCtx.opts.record) + sys.exit(0) From 5a4764b53a5cfb9413d9826e8b22086cf1b5c5d4 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 19 Sep 2025 16:48:20 +0200 Subject: [PATCH 30/56] all tests in test-data-2.0 working --- python/fileTests-2.0.py | 2 +- python/fileTestsLib.py | 22 +++++++----- python/src/errors.py | 25 ++++++++++---- python/src/i18n.py | 34 ++++++++++++++----- python/src/utils.py | 4 +++ python/test-data-2.0/constructor.err | 17 ++++++++++ python/test-data-2.0/constructor.err_en | 17 ++++++++++ python/test-data-2.0/constructor.out | 0 python/test-data-2.0/constructor.out_en | 0 ...aclassOk.py => constructorDataclass_ok.py} | 0 python/test-data-2.0/constructor_ok.err | 0 python/test-data-2.0/constructor_ok.out | 1 + python/test-data-2.0/forwardRefs.err | 17 ++++++++++ python/test-data-2.0/forwardRefs.err_en | 17 ++++++++++ python/test-data-2.0/forwardRefs.out | 0 python/test-data-2.0/forwardRefs.out_en | 0 python/test-data-2.0/forwardRefs_ok.err | 0 python/test-data-2.0/forwardRefs_ok.out | 1 + python/test-data-2.0/functionArg.err | 2 +- python/test-data-2.0/functionArg.err_en | 2 +- python/test-data-2.0/functionArg_ok.err | 0 python/test-data-2.0/functionArg_ok.err_None | 0 python/test-data-2.0/functionArg_ok.out | 1 + python/test-data-2.0/functionArg_ok.out_None | 1 + python/test-data-2.0/functionNoResult.err | 23 +++++++++++++ python/test-data-2.0/functionNoResult.err_en | 23 +++++++++++++ python/test-data-2.0/functionNoResult.out | 0 python/test-data-2.0/functionNoResult.out_en | 0 python/test-data-2.0/functionNoResult2.err | 19 +++++++++++ python/test-data-2.0/functionNoResult2.err_en | 19 +++++++++++ python/test-data-2.0/functionNoResult2.out | 0 python/test-data-2.0/functionNoResult2.out_en | 0 python/test-data-2.0/functionNoResult2_ok.err | 0 python/test-data-2.0/functionNoResult2_ok.out | 1 + python/test-data-2.0/functionNoResult3.err | 19 +++++++++++ python/test-data-2.0/functionNoResult3.err_en | 19 +++++++++++ python/test-data-2.0/functionNoResult3.out | 0 python/test-data-2.0/functionNoResult3.out_en | 0 python/test-data-2.0/functionNoResult3_ok.err | 0 python/test-data-2.0/functionNoResult3_ok.out | 1 + python/test-data-2.0/functionNoResult_ok.err | 0 python/test-data-2.0/functionNoResult_ok.out | 1 + python/test-data-2.0/functionResult.err | 23 +++++++++++++ python/test-data-2.0/functionResult.err_en | 23 +++++++++++++ python/test-data-2.0/functionResult.out | 0 python/test-data-2.0/functionResult.out_en | 0 python/test-data-2.0/functionResult2.err | 19 +++++++++++ python/test-data-2.0/functionResult2.err_en | 19 +++++++++++ python/test-data-2.0/functionResult2.out | 0 python/test-data-2.0/functionResult2.out_en | 0 python/test-data-2.0/functionResult_ok.err | 0 python/test-data-2.0/functionResult_ok.out | 1 + python/test-data-2.0/iterator.err | 26 +++++++++----- python/test-data-2.0/iterator.err_en | 23 +++++++++++++ python/test-data-2.0/iterator.out | 0 python/test-data-2.0/iterator.out_en | 0 python/test-data-2.0/iterator_ok.err | 0 python/test-data-2.0/iterator_ok.out | 3 ++ python/test-data-2.0/listArg.err | 16 +++++++++ python/test-data-2.0/listArg.err_en | 16 +++++++++ python/test-data-2.0/listArg.out | 0 python/test-data-2.0/listArg.out_en | 0 python/test-data-2.0/listArg_ok.err | 0 python/test-data-2.0/listArg_ok.out | 1 + python/test-data-2.0/listResult.err | 22 ++++++++++++ python/test-data-2.0/listResult.err_en | 22 ++++++++++++ python/test-data-2.0/listResult.out | 0 python/test-data-2.0/listResult.out_en | 0 python/test-data-2.0/listResult_ok.err | 0 python/test-data-2.0/listResult_ok.out | 1 + python/test-data-2.0/method.err | 17 ++++++++++ python/test-data-2.0/method.err_en | 17 ++++++++++ python/test-data-2.0/method.out | 0 python/test-data-2.0/method.out_en | 0 python/test-data-2.0/method_ok.err | 0 python/test-data-2.0/method_ok.out | 1 + python/test-data-2.0/partial.err | 10 ++++++ python/test-data-2.0/partial.err_en | 10 ++++++ python/test-data-2.0/partial.out | 0 python/test-data-2.0/partial.out_en | 0 python/test-data-2.0/record.err | 17 ++++++++++ python/test-data-2.0/record.err_en | 17 ++++++++++ python/test-data-2.0/record.out | 0 python/test-data-2.0/record.out_en | 0 python/test-data-2.0/record_ok.err | 0 python/test-data-2.0/record_ok.out | 1 + python/test-data-2.0/staticmethod.err | 17 ++++++++++ python/test-data-2.0/staticmethod.err_en | 17 ++++++++++ python/test-data-2.0/staticmethod.out | 0 python/test-data-2.0/staticmethod.out_en | 0 python/test-data-2.0/staticmethod_ok.err | 0 python/test-data-2.0/staticmethod_ok.out | 1 + 92 files changed, 594 insertions(+), 34 deletions(-) create mode 100644 python/test-data-2.0/constructor.err create mode 100644 python/test-data-2.0/constructor.err_en create mode 100644 python/test-data-2.0/constructor.out create mode 100644 python/test-data-2.0/constructor.out_en rename python/test-data-2.0/{constructorDataclassOk.py => constructorDataclass_ok.py} (100%) create mode 100644 python/test-data-2.0/constructor_ok.err create mode 100644 python/test-data-2.0/constructor_ok.out create mode 100644 python/test-data-2.0/forwardRefs.err create mode 100644 python/test-data-2.0/forwardRefs.err_en create mode 100644 python/test-data-2.0/forwardRefs.out create mode 100644 python/test-data-2.0/forwardRefs.out_en create mode 100644 python/test-data-2.0/forwardRefs_ok.err create mode 100644 python/test-data-2.0/forwardRefs_ok.out create mode 100644 python/test-data-2.0/functionArg_ok.err create mode 100644 python/test-data-2.0/functionArg_ok.err_None create mode 100644 python/test-data-2.0/functionArg_ok.out create mode 100644 python/test-data-2.0/functionArg_ok.out_None create mode 100644 python/test-data-2.0/functionNoResult.err create mode 100644 python/test-data-2.0/functionNoResult.err_en create mode 100644 python/test-data-2.0/functionNoResult.out create mode 100644 python/test-data-2.0/functionNoResult.out_en create mode 100644 python/test-data-2.0/functionNoResult2.err create mode 100644 python/test-data-2.0/functionNoResult2.err_en create mode 100644 python/test-data-2.0/functionNoResult2.out create mode 100644 python/test-data-2.0/functionNoResult2.out_en create mode 100644 python/test-data-2.0/functionNoResult2_ok.err create mode 100644 python/test-data-2.0/functionNoResult2_ok.out create mode 100644 python/test-data-2.0/functionNoResult3.err create mode 100644 python/test-data-2.0/functionNoResult3.err_en create mode 100644 python/test-data-2.0/functionNoResult3.out create mode 100644 python/test-data-2.0/functionNoResult3.out_en create mode 100644 python/test-data-2.0/functionNoResult3_ok.err create mode 100644 python/test-data-2.0/functionNoResult3_ok.out create mode 100644 python/test-data-2.0/functionNoResult_ok.err create mode 100644 python/test-data-2.0/functionNoResult_ok.out create mode 100644 python/test-data-2.0/functionResult.err create mode 100644 python/test-data-2.0/functionResult.err_en create mode 100644 python/test-data-2.0/functionResult.out create mode 100644 python/test-data-2.0/functionResult.out_en create mode 100644 python/test-data-2.0/functionResult2.err create mode 100644 python/test-data-2.0/functionResult2.err_en create mode 100644 python/test-data-2.0/functionResult2.out create mode 100644 python/test-data-2.0/functionResult2.out_en create mode 100644 python/test-data-2.0/functionResult_ok.err create mode 100644 python/test-data-2.0/functionResult_ok.out create mode 100644 python/test-data-2.0/iterator.err_en create mode 100644 python/test-data-2.0/iterator.out create mode 100644 python/test-data-2.0/iterator.out_en create mode 100644 python/test-data-2.0/iterator_ok.err create mode 100644 python/test-data-2.0/iterator_ok.out create mode 100644 python/test-data-2.0/listArg.err create mode 100644 python/test-data-2.0/listArg.err_en create mode 100644 python/test-data-2.0/listArg.out create mode 100644 python/test-data-2.0/listArg.out_en create mode 100644 python/test-data-2.0/listArg_ok.err create mode 100644 python/test-data-2.0/listArg_ok.out create mode 100644 python/test-data-2.0/listResult.err create mode 100644 python/test-data-2.0/listResult.err_en create mode 100644 python/test-data-2.0/listResult.out create mode 100644 python/test-data-2.0/listResult.out_en create mode 100644 python/test-data-2.0/listResult_ok.err create mode 100644 python/test-data-2.0/listResult_ok.out create mode 100644 python/test-data-2.0/method.err create mode 100644 python/test-data-2.0/method.err_en create mode 100644 python/test-data-2.0/method.out create mode 100644 python/test-data-2.0/method.out_en create mode 100644 python/test-data-2.0/method_ok.err create mode 100644 python/test-data-2.0/method_ok.out create mode 100644 python/test-data-2.0/partial.err create mode 100644 python/test-data-2.0/partial.err_en create mode 100644 python/test-data-2.0/partial.out create mode 100644 python/test-data-2.0/partial.out_en create mode 100644 python/test-data-2.0/record.err create mode 100644 python/test-data-2.0/record.err_en create mode 100644 python/test-data-2.0/record.out create mode 100644 python/test-data-2.0/record.out_en create mode 100644 python/test-data-2.0/record_ok.err create mode 100644 python/test-data-2.0/record_ok.out create mode 100644 python/test-data-2.0/staticmethod.err create mode 100644 python/test-data-2.0/staticmethod.err_en create mode 100644 python/test-data-2.0/staticmethod.out create mode 100644 python/test-data-2.0/staticmethod.out_en create mode 100644 python/test-data-2.0/staticmethod_ok.err create mode 100644 python/test-data-2.0/staticmethod_ok.out diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index 722090e2..cf8ae9e1 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -7,6 +7,6 @@ if file.is_file(): name = file.as_posix() if name.endswith('.py'): - checkBasic(name) + check(name) globalCtx.results.finish() diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index dcb3658a..0bf30dd6 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -16,7 +16,7 @@ class TestOpts: only: Optional[str] keepGoing: bool record: Optional[str] - lang: str + lang: Optional[str] def parseArgs() -> TestOpts: parser = argparse.ArgumentParser( @@ -102,7 +102,9 @@ def readFileIfExists(filePath: str) -> str: else: return readFile(filePath) -def getVersionedFile(base: str, typcheck: bool, lang: str) -> str: +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 @@ -189,6 +191,8 @@ def fixOutput(filePath: str): 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 "/[^"]*"', ' File ""', content, flags=re.MULTILINE) # Remove file paths + content = re.sub(r'## File .*/([^/]*)$', '## File \\1', content, flags=re.MULTILINE) + content = re.sub(r'## Datei .*/([^/]*)$', '## Datei \\1', content, flags=re.MULTILINE) with open(filePath, 'w') as f: f.write(content) @@ -219,6 +223,7 @@ def _runTest(testFile: str, cmd.extend(args) env = os.environ.copy() env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) + env['WYPP_UNDER_TEST'] = 'True' with open(actualStdoutFile, 'w') as stdoutFile, \ open(actualStderrFile, 'w') as stderrFile: # Run the command @@ -297,6 +302,9 @@ def _check(testFile: str, else: return 'passed' +def guessExitCode(testFile: str) -> int: + return 0 if testFile.endswith('_ok.py') else 1 + def check(testFile: str, exitCode: int = 1, typecheck: bool = True, @@ -306,6 +314,8 @@ def check(testFile: str, 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.record(testFile, status) if status == 'failed': @@ -321,18 +331,14 @@ def checkBoth(testFile: str, check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') def checkBasic(testFile: str, ctx: TestContext = globalCtx): - if testFile.endswith('_ok.py'): - expectedExitCode = 0 - else: - expectedExitCode = 1 - check(testFile, exitCode=expectedExitCode, checkOutputs=False, ctx=ctx) + 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 = 0 if testFile.endswith('_ok.py') else 1 + exitCode = guessExitCode(testFile) typecheck = True args = [] pythonPath = [] diff --git a/python/src/errors.py b/python/src/errors.py index 6a056299..0e605ed6 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -36,6 +36,15 @@ 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 + class WyppTypeError(TypeError, WyppError): def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []): @@ -53,7 +62,7 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc extraFrames: list[inspect.FrameInfo]) -> WyppTypeError: lines = [] if givenValue is None: - lines.append('no return found') + lines.append(i18n.tr('no result returned')) else: lines.append(renderGiven(givenValue, returnLoc)) lines.append('') @@ -68,8 +77,9 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc else: # result type expected but different type given lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy))) - if not isParameterizedType(resultTy): - lines.append(i18n.wrongReturnValue(renderTy(givenValue))) + givenTy = type(givenValue) + if shouldReportTyMismatch(resultTy, givenTy): + lines.append(i18n.wrongReturnValue(renderTy(givenTy))) printedFileName = None if resultTypeLoc: lines.append('') @@ -77,7 +87,7 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc printedFileName = resultTypeLoc.filename lines.append(f'## {i18n.tr("Result type declared in line")} {resultTypeLoc.startLine}:\n') lines.append(renderLoc(resultTypeLoc)) - if returnLoc: + if givenValue is not None and returnLoc: lines.append('') if printedFileName != returnLoc.filename: lines.append(f'## {i18n.tr("File")} {returnLoc.filename}') @@ -88,7 +98,10 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc lines.append('') if printedFileName != callLoc.filename: lines.append(f'## {i18n.tr("File")} {callLoc.filename}') - lines.append(f'## {i18n.tr("Call causing the problematic return in line")} {callLoc.startLine}:\n') + if givenValue is None: + lines.append(f'## {i18n.unexpectedNoReturn(callLoc.startLine)}\n') + else: + lines.append(f'## {i18n.unexpectedReturn(callLoc.startLine)}\n') lines.append(renderLoc(callLoc)) raise WyppTypeError('\n'.join(lines), extraFrames) @@ -100,7 +113,7 @@ def argumentError(callableName: location.CallableName, paramName: str, paramInde lines.append(givenStr) lines.append('') lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramIndex + 1)) - if not isParameterizedType(paramTy): + if shouldReportTyMismatch(paramTy, type(givenValue)): lines.append(i18n.realArgumentTy(renderTy(type(givenValue)))) if givenLoc: lines.append('') diff --git a/python/src/i18n.py b/python/src/i18n.py index 168dc633..54931551 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -3,6 +3,7 @@ from typing import * from contextlib import contextmanager import lang +import utils type Lang = Literal['en', 'de'] @@ -32,12 +33,17 @@ def setLang(lang: Lang): global _lang _lang = lang -def tr(key, **kws) -> str: +def tr(key: str, **kws) -> str: match getLang(): case 'en': tmpl = key case 'de': - tmpl = DE[key] + 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 = { @@ -49,17 +55,20 @@ def tr(key, **kws) -> str: 'Kein Rückgabewert erwartet bei Aufruf des Konstruktors der Klasse `{cls}`.', 'Expecting return value of type `{ty}` when calling function `{fun}`.': - 'Rückgabewert vom Typ `{ty} erwartet bei Aufruf der Funktion `{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}`.', + 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf von Methode `{method}` aus Klasse `{cls}`.', 'Expecting return value of type `{ty}` when calling constructor of class `{cls}`.': - 'Rückgabewert vom Typ `{ty} erwartet bei Aufruf des Konstruktors der Klasse `{cls}`.', + 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf des Konstruktors der Klasse `{cls}`.', 'But the call returns a value of type `{ty}`.': - 'Aber der Aufruf gibt einen Wert vom Typ `{ty} zurück.', + 'Aber der Aufruf gibt einen Wert vom Typ `{ty}` zurück.', - 'But no return found.': + '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}.', @@ -76,7 +85,8 @@ def tr(key, **kws) -> str: '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 causing the problematic return in line': 'Aufruf, der das fehlerhafte return verursacht, 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 Typangabe.', @@ -105,6 +115,12 @@ def expectingNoReturn(cn: location.CallableName) -> str: 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': @@ -119,7 +135,7 @@ def expectingReturnOfType(cn: location.CallableName, ty: str) -> str: raise ValueError(f'Unexpected: {cn}') def noReturnValue() -> str: - return tr('But no return found.') + return tr('But no value returned.') def transArg(pos: int): match getLang(): diff --git a/python/src/utils.py b/python/src/utils.py index fbbaca7e..5a2d4343 100644 --- a/python/src/utils.py +++ b/python/src/utils.py @@ -26,3 +26,7 @@ def dropWhile(l: list, f: Callable[[Any], bool]) -> list: if not f(l[i]): break return l[i:] + +def isUnderTest() -> bool: + x = os.getenv('WYPP_UNDER_TEST') + return x == 'True' diff --git a/python/test-data-2.0/constructor.err b/python/test-data-2.0/constructor.err new file mode 100644 index 00000000..f9beaa9a --- /dev/null +++ b/python/test-data-2.0/constructor.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 constructor.py +## Fehlerhafter Aufruf in Zeile 5: + +c = C("1") + +## Typ deklariert in Zeile 2: + + def __init__(self, x: int): diff --git a/python/test-data-2.0/constructor.err_en b/python/test-data-2.0/constructor.err_en new file mode 100644 index 00000000..40012443 --- /dev/null +++ b/python/test-data-2.0/constructor.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 constructor.py +## Problematic call in line 5: + +c = C("1") + +## Type declared in line 2: + + def __init__(self, x: int): diff --git a/python/test-data-2.0/constructor.out b/python/test-data-2.0/constructor.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/constructor.out_en b/python/test-data-2.0/constructor.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/constructorDataclassOk.py b/python/test-data-2.0/constructorDataclass_ok.py similarity index 100% rename from python/test-data-2.0/constructorDataclassOk.py rename to python/test-data-2.0/constructorDataclass_ok.py diff --git a/python/test-data-2.0/constructor_ok.err b/python/test-data-2.0/constructor_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/constructor_ok.out b/python/test-data-2.0/constructor_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/constructor_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/forwardRefs.err b/python/test-data-2.0/forwardRefs.err new file mode 100644 index 00000000..dd3063dc --- /dev/null +++ b/python/test-data-2.0/forwardRefs.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 forwardRefs.py +## Fehlerhafter Aufruf in Zeile 12: + +a.foo(a) + +## Typ deklariert in Zeile 4: + + def foo(self, b: B): diff --git a/python/test-data-2.0/forwardRefs.err_en b/python/test-data-2.0/forwardRefs.err_en new file mode 100644 index 00000000..dd6ad4e5 --- /dev/null +++ b/python/test-data-2.0/forwardRefs.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 forwardRefs.py +## Problematic call in line 12: + +a.foo(a) + +## Type declared in line 4: + + def foo(self, b: B): diff --git a/python/test-data-2.0/forwardRefs.out b/python/test-data-2.0/forwardRefs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/forwardRefs.out_en b/python/test-data-2.0/forwardRefs.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/forwardRefs_ok.err b/python/test-data-2.0/forwardRefs_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/forwardRefs_ok.out b/python/test-data-2.0/forwardRefs_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/forwardRefs_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionArg.err b/python/test-data-2.0/functionArg.err index a5144416..1b87c548 100644 --- a/python/test-data-2.0/functionArg.err +++ b/python/test-data-2.0/functionArg.err @@ -7,7 +7,7 @@ WyppTypeError: "1" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als zweites Argument. Aber der übergebene Wert hat Typ `str`. -## Datei /Users/swehr/devel/write-your-python-program/python/test-data-2.0/functionArg.py +## Datei functionArg.py ## Fehlerhafter Aufruf in Zeile 8: print(foo(foo(1, "1"), 42)) diff --git a/python/test-data-2.0/functionArg.err_en b/python/test-data-2.0/functionArg.err_en index 57ef31d2..b6b52ae5 100644 --- a/python/test-data-2.0/functionArg.err_en +++ b/python/test-data-2.0/functionArg.err_en @@ -7,7 +7,7 @@ WyppTypeError: "1" The call of function `foo` expects value of type `int` as 2nd argument. But the value given has type `str`. -## File /Users/swehr/devel/write-your-python-program/python/test-data-2.0/functionArg.py +## File functionArg.py ## Problematic call in line 8: print(foo(foo(1, "1"), 42)) diff --git a/python/test-data-2.0/functionArg_ok.err b/python/test-data-2.0/functionArg_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionArg_ok.err_None b/python/test-data-2.0/functionArg_ok.err_None new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionArg_ok.out b/python/test-data-2.0/functionArg_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionArg_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionArg_ok.out_None b/python/test-data-2.0/functionArg_ok.out_None new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionArg_ok.out_None @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionNoResult.err b/python/test-data-2.0/functionNoResult.err new file mode 100644 index 00000000..e8656f9d --- /dev/null +++ b/python/test-data-2.0/functionNoResult.err @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", 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 functionNoResult.py +## Rückgabetyp deklariert in Zeile 1: + +def foo(i: int) -> None: + +## Fehlerhaftes return in Zeile 2: + + return "x" + +## Aufruf in Zeile 4 verursacht das fehlerhafte return: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult.err_en b/python/test-data-2.0/functionNoResult.err_en new file mode 100644 index 00000000..923f6139 --- /dev/null +++ b/python/test-data-2.0/functionNoResult.err_en @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", 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 functionNoResult.py +## Result type declared in line 1: + +def foo(i: int) -> None: + +## Problematic return in line 2: + + return "x" + +## Call in line 4 causes the problematic return: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult.out b/python/test-data-2.0/functionNoResult.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult.out_en b/python/test-data-2.0/functionNoResult.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult2.err b/python/test-data-2.0/functionNoResult2.err new file mode 100644 index 00000000..7546ff89 --- /dev/null +++ b/python/test-data-2.0/functionNoResult2.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", 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 functionNoResult2.py +## Rückgabetyp deklariert in Zeile 1: + +def foo(i: int) -> int: + +## Aufruf in Zeile 4 führt dazu, dass die Funktion keinen Wert zurückgibt: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult2.err_en b/python/test-data-2.0/functionNoResult2.err_en new file mode 100644 index 00000000..34095d71 --- /dev/null +++ b/python/test-data-2.0/functionNoResult2.err_en @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", line 2, in foo + pass + +WyppTypeError: no result returned + +Expecting return value of type `int` when calling function `foo`. +But no value returned. + +## File functionNoResult2.py +## Result type declared in line 1: + +def foo(i: int) -> int: + +## Call in line 4 causes the function to return no value: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult2.out b/python/test-data-2.0/functionNoResult2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult2.out_en b/python/test-data-2.0/functionNoResult2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult2_ok.err b/python/test-data-2.0/functionNoResult2_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult2_ok.out b/python/test-data-2.0/functionNoResult2_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionNoResult2_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionNoResult3.err b/python/test-data-2.0/functionNoResult3.err new file mode 100644 index 00000000..db1dc09f --- /dev/null +++ b/python/test-data-2.0/functionNoResult3.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", 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 functionNoResult3.py +## Fehlerhaftes return in Zeile 2: + + return "x" + +## Aufruf in Zeile 4 verursacht das fehlerhafte return: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult3.err_en b/python/test-data-2.0/functionNoResult3.err_en new file mode 100644 index 00000000..90890d8c --- /dev/null +++ b/python/test-data-2.0/functionNoResult3.err_en @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + File "", 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 functionNoResult3.py +## Problematic return in line 2: + + return "x" + +## Call in line 4 causes the problematic return: + +foo(1) diff --git a/python/test-data-2.0/functionNoResult3.out b/python/test-data-2.0/functionNoResult3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult3.out_en b/python/test-data-2.0/functionNoResult3.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult3_ok.err b/python/test-data-2.0/functionNoResult3_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult3_ok.out b/python/test-data-2.0/functionNoResult3_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionNoResult3_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionNoResult_ok.err b/python/test-data-2.0/functionNoResult_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionNoResult_ok.out b/python/test-data-2.0/functionNoResult_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionNoResult_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/functionResult.err b/python/test-data-2.0/functionResult.err new file mode 100644 index 00000000..3fd93c1a --- /dev/null +++ b/python/test-data-2.0/functionResult.err @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "", line 7, in + foo(1) + File "", 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 functionResult.py +## Rückgabetyp deklariert in Zeile 4: + +def foo(i: int) -> int: + +## Fehlerhaftes return in Zeile 5: + + return "foo_" + str(i) + +## Aufruf in Zeile 7 verursacht das fehlerhafte return: + +foo(1) diff --git a/python/test-data-2.0/functionResult.err_en b/python/test-data-2.0/functionResult.err_en new file mode 100644 index 00000000..15e990e6 --- /dev/null +++ b/python/test-data-2.0/functionResult.err_en @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "", line 7, in + foo(1) + File "", 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 functionResult.py +## Result type declared in line 4: + +def foo(i: int) -> int: + +## Problematic return in line 5: + + return "foo_" + str(i) + +## Call in line 7 causes the problematic return: + +foo(1) diff --git a/python/test-data-2.0/functionResult.out b/python/test-data-2.0/functionResult.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult.out_en b/python/test-data-2.0/functionResult.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult2.err b/python/test-data-2.0/functionResult2.err new file mode 100644 index 00000000..a17bb953 --- /dev/null +++ b/python/test-data-2.0/functionResult2.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 7, in + foo(1) + File "", 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 functionResult2.py +## Fehlerhaftes return in Zeile 5: + + return "foo_" + str(i) + +## Aufruf in Zeile 7 verursacht das fehlerhafte return: + +foo(1) diff --git a/python/test-data-2.0/functionResult2.err_en b/python/test-data-2.0/functionResult2.err_en new file mode 100644 index 00000000..c1c439ce --- /dev/null +++ b/python/test-data-2.0/functionResult2.err_en @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "", line 7, in + foo(1) + File "", 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 functionResult2.py +## Problematic return in line 5: + + return "foo_" + str(i) + +## Call in line 7 causes the problematic return: + +foo(1) diff --git a/python/test-data-2.0/functionResult2.out b/python/test-data-2.0/functionResult2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult2.out_en b/python/test-data-2.0/functionResult2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult_ok.err b/python/test-data-2.0/functionResult_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/functionResult_ok.out b/python/test-data-2.0/functionResult_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/functionResult_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/iterator.err b/python/test-data-2.0/iterator.err index b414d2ea..ac55b12b 100644 --- a/python/test-data-2.0/iterator.err +++ b/python/test-data-2.0/iterator.err @@ -1,13 +1,23 @@ Traceback (most recent call last): - ... + File "", line 6, in + g = my_generator() + File "", line 4, in my_generator + return 1 -WyppTypeError: got value of wrong type -Given: 1 -Expected: value of type Iterator[int] +WyppTypeError: 1 -Location: test-data-2.0/iterator.err, line 4 +Rückgabewert vom Typ `Iterator[int]` erwartet bei Aufruf der Funktion `my_generator`. +Aber der Aufruf gibt einen Wert vom Typ `int` zurück. -Context: +## Datei iterator.py +## Rückgabetyp deklariert in Zeile 3: -3| def my_generator() -> <>: -4| return <> +def my_generator() -> Iterator[int]: + +## Fehlerhaftes return in Zeile 4: + + return 1 + +## Aufruf in Zeile 6 verursacht das fehlerhafte return: + +g = my_generator() diff --git a/python/test-data-2.0/iterator.err_en b/python/test-data-2.0/iterator.err_en new file mode 100644 index 00000000..d0716ee0 --- /dev/null +++ b/python/test-data-2.0/iterator.err_en @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "", line 6, in + g = my_generator() + File "", 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 iterator.py +## Result type declared in line 3: + +def my_generator() -> Iterator[int]: + +## Problematic return in line 4: + + return 1 + +## Call in line 6 causes the problematic return: + +g = my_generator() diff --git a/python/test-data-2.0/iterator.out b/python/test-data-2.0/iterator.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/iterator.out_en b/python/test-data-2.0/iterator.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/iterator_ok.err b/python/test-data-2.0/iterator_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/iterator_ok.out b/python/test-data-2.0/iterator_ok.out new file mode 100644 index 00000000..7e3104bf --- /dev/null +++ b/python/test-data-2.0/iterator_ok.out @@ -0,0 +1,3 @@ +6 +7 +ok diff --git a/python/test-data-2.0/listArg.err b/python/test-data-2.0/listArg.err new file mode 100644 index 00000000..99748a6f --- /dev/null +++ b/python/test-data-2.0/listArg.err @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "", 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 listArg.py +## Fehlerhafter Aufruf in Zeile 4: + +foo([1, 2, "3"]) + +## Typ deklariert in Zeile 1: + +def foo(l: list[int]) -> int: diff --git a/python/test-data-2.0/listArg.err_en b/python/test-data-2.0/listArg.err_en new file mode 100644 index 00000000..760497f3 --- /dev/null +++ b/python/test-data-2.0/listArg.err_en @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "", 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 listArg.py +## Problematic call in line 4: + +foo([1, 2, "3"]) + +## Type declared in line 1: + +def foo(l: list[int]) -> int: diff --git a/python/test-data-2.0/listArg.out b/python/test-data-2.0/listArg.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listArg.out_en b/python/test-data-2.0/listArg.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listArg_ok.err b/python/test-data-2.0/listArg_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listArg_ok.out b/python/test-data-2.0/listArg_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/listArg_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/listResult.err b/python/test-data-2.0/listResult.err new file mode 100644 index 00000000..47f72a14 --- /dev/null +++ b/python/test-data-2.0/listResult.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "", line 5, in + foo([1, 2, 3]) + File "", line 3, in foo + return l + +WyppTypeError: [1, 2, 3, '4'] + +Rückgabewert vom Typ `list[int]` erwartet bei Aufruf der Funktion `foo`. + +## Datei listResult.py +## Rückgabetyp deklariert in Zeile 1: + +def foo(l: list[int]) -> list[int]: + +## Fehlerhaftes return in Zeile 3: + + return l + +## Aufruf in Zeile 5 verursacht das fehlerhafte return: + +foo([1, 2, 3]) diff --git a/python/test-data-2.0/listResult.err_en b/python/test-data-2.0/listResult.err_en new file mode 100644 index 00000000..13f7a42a --- /dev/null +++ b/python/test-data-2.0/listResult.err_en @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "", line 5, in + foo([1, 2, 3]) + File "", line 3, in foo + return l + +WyppTypeError: [1, 2, 3, '4'] + +Expecting return value of type `list[int]` when calling function `foo`. + +## File listResult.py +## Result type declared in line 1: + +def foo(l: list[int]) -> list[int]: + +## Problematic return in line 3: + + return l + +## Call in line 5 causes the problematic return: + +foo([1, 2, 3]) diff --git a/python/test-data-2.0/listResult.out b/python/test-data-2.0/listResult.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listResult.out_en b/python/test-data-2.0/listResult.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listResult_ok.err b/python/test-data-2.0/listResult_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/listResult_ok.out b/python/test-data-2.0/listResult_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/listResult_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/method.err b/python/test-data-2.0/method.err new file mode 100644 index 00000000..632ce4b3 --- /dev/null +++ b/python/test-data-2.0/method.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 method.py +## Fehlerhafter Aufruf in Zeile 9: + +c.method("2") + +## Typ deklariert in Zeile 5: + + def method(self, y: int) -> int: diff --git a/python/test-data-2.0/method.err_en b/python/test-data-2.0/method.err_en new file mode 100644 index 00000000..88563a4f --- /dev/null +++ b/python/test-data-2.0/method.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 method.py +## Problematic call in line 9: + +c.method("2") + +## Type declared in line 5: + + def method(self, y: int) -> int: diff --git a/python/test-data-2.0/method.out b/python/test-data-2.0/method.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/method.out_en b/python/test-data-2.0/method.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/method_ok.err b/python/test-data-2.0/method_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/method_ok.out b/python/test-data-2.0/method_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/method_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/partial.err b/python/test-data-2.0/partial.err new file mode 100644 index 00000000..7c3f2643 --- /dev/null +++ b/python/test-data-2.0/partial.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1, 2) + +WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typangabe. + +## Datei partial.py +## Parameter deklariert in Zeile 1: + +def foo(i: int, j) -> None: diff --git a/python/test-data-2.0/partial.err_en b/python/test-data-2.0/partial.err_en new file mode 100644 index 00000000..d8609349 --- /dev/null +++ b/python/test-data-2.0/partial.err_en @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1, 2) + +WyppTypeError: Parameter `j` of function `foo` requires a type annotation. + +## File partial.py +## Parameter declared in line 1: + +def foo(i: int, j) -> None: diff --git a/python/test-data-2.0/partial.out b/python/test-data-2.0/partial.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/partial.out_en b/python/test-data-2.0/partial.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/record.err b/python/test-data-2.0/record.err new file mode 100644 index 00000000..0f3bb7b8 --- /dev/null +++ b/python/test-data-2.0/record.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 8, in + p = Person("Alice", "30") + +WyppTypeError: "30" + +Der Aufruf des Konstruktors der Klasse `Person` erwartet Wert vom Typ `int` als zweites Argument. +Aber der übergebene Wert hat Typ `str`. + +## Datei record.py +## Fehlerhafter Aufruf in Zeile 8: + +p = Person("Alice", "30") + +## Typ deklariert in Zeile 6: + + age: int diff --git a/python/test-data-2.0/record.err_en b/python/test-data-2.0/record.err_en new file mode 100644 index 00000000..7e5cc156 --- /dev/null +++ b/python/test-data-2.0/record.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 8, in + p = Person("Alice", "30") + +WyppTypeError: "30" + +The call of the constructor of class `Person` expects value of type `int` as 2nd argument. +But the value given has type `str`. + +## File record.py +## Problematic call in line 8: + +p = Person("Alice", "30") + +## Type declared in line 6: + + age: int diff --git a/python/test-data-2.0/record.out b/python/test-data-2.0/record.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/record.out_en b/python/test-data-2.0/record.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/record_ok.err b/python/test-data-2.0/record_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/record_ok.out b/python/test-data-2.0/record_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/record_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/staticmethod.err b/python/test-data-2.0/staticmethod.err new file mode 100644 index 00000000..a7f862ce --- /dev/null +++ b/python/test-data-2.0/staticmethod.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 staticmethod.py +## Fehlerhafter Aufruf in Zeile 6: + +C.method("2") + +## Typ deklariert in Zeile 3: + + def method(y: int) -> int: diff --git a/python/test-data-2.0/staticmethod.err_en b/python/test-data-2.0/staticmethod.err_en new file mode 100644 index 00000000..3690c706 --- /dev/null +++ b/python/test-data-2.0/staticmethod.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 staticmethod.py +## Problematic call in line 6: + +C.method("2") + +## Type declared in line 3: + + def method(y: int) -> int: diff --git a/python/test-data-2.0/staticmethod.out b/python/test-data-2.0/staticmethod.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/staticmethod.out_en b/python/test-data-2.0/staticmethod.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/staticmethod_ok.err b/python/test-data-2.0/staticmethod_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/staticmethod_ok.out b/python/test-data-2.0/staticmethod_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/staticmethod_ok.out @@ -0,0 +1 @@ +ok From 51092b91549948ef60afe9a934576ed95386b0fe Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Fri, 19 Sep 2025 16:49:31 +0200 Subject: [PATCH 31/56] add missing files --- python/src/lang.py | 51 ++++++++++++++++++++++++++++ python/src/parsecache.py | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 python/src/lang.py create mode 100644 python/src/parsecache.py diff --git a/python/src/lang.py b/python/src/lang.py new file mode 100644 index 00000000..e19a7b6f --- /dev/null +++ b/python/src/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/src/parsecache.py b/python/src/parsecache.py new file mode 100644 index 00000000..8e05b540 --- /dev/null +++ b/python/src/parsecache.py @@ -0,0 +1,72 @@ +import ast +import os +import linecache + +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 getFunDef(self, name: str) -> ast.FunctionDef | ast.AsyncFunctionDef | None: + if name in self.__cache: + return self.__cache[name] + self.__ensureTree() + resultNode = None + if self.__tree: + for node in ast.walk(self.__tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name: + resultNode = node + break + self.__cache[name] = resultNode + return resultNode + + def getRecordAttr(self, clsName: str, attrName: str) -> ast.AnnAssign | None: + key = (clsName, attrName) + if key in self.__cache: + return self.__cache[key] + self.__ensureTree() + # Step 1: find the class + targetCls = None + if self.__tree: + for node in ast.walk(self.__tree): + if isinstance(node, ast.ClassDef) and node.name == clsName: + targetCls = node + break + # Step 2: look for an AnnAssign like: A: T = ... + annNode = None + if targetCls: + for stmt in targetCls.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + if stmt.target.id == attrName: + annNode = stmt + self.__cache[key] = annNode + return annNode + +_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] From 37dfe4fc42c462ba8bab218cbf066ffcf86c743b Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 08:21:01 +0200 Subject: [PATCH 32/56] support for functions/methods without annotations --- python/TODO_nowrappers.md | 4 +++- python/src/typecheck.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index de6a0bb5..f95edf1b 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,3 +1,5 @@ -* Fix unit tests in test-data +* Fix file tests in test-data +* Integration tests +* Installation * Typechecked console * Debug slow startup times diff --git a/python/src/typecheck.py b/python/src/typecheck.py index 8f80ab4f..87f38ebb 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -18,6 +18,13 @@ def printVars(what: str, *l): 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 checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, code: location.CallableInfo, cfg: CheckCfg) -> None: params = list(sig.parameters) @@ -109,8 +116,10 @@ def getNamespacesOfCallable(func: Callable): def wrapTypecheck(cfg: dict, outerCode: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: outerCheckCfg = CheckCfg.fromDict(cfg) def _wrap(f: Callable[P, T]) -> Callable[P, T]: - checkCfg = outerCheckCfg.setNamespaces(getNamespacesOfCallable(f)) sig = inspect.signature(f) + if isEmptySignature(sig): + return f + checkCfg = outerCheckCfg.setNamespaces(getNamespacesOfCallable(f)) if outerCode is None: code = location.StdCallableInfo(f, checkCfg.kind) else: From 2c9ff590307f9ac8efedbf5902e42536e1aafdd7 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 09:09:24 +0200 Subject: [PATCH 33/56] refactored formatting of types --- python/src/errors.py | 2 +- python/src/myTypeguard.py | 9 ++-- python/test-data-2.0/nested.err | 17 ++++++ python/test-data-2.0/nested.out | 0 python/test-data-2.0/nested.py | 4 ++ python/test-data-2.0/nosig_ok.err | 0 python/test-data-2.0/nosig_ok.out | 1 + python/test-data-2.0/nosig_ok.py | 14 +++++ python/test-data-2.0/testCallable.err | 17 ++++++ python/test-data-2.0/testCallable.out | 0 python/test-data-2.0/testCallable.py | 6 +++ python/tests/test_renderTy.py | 75 +++++++++++++++++++++++++++ 12 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 python/test-data-2.0/nested.err create mode 100644 python/test-data-2.0/nested.out create mode 100644 python/test-data-2.0/nested.py create mode 100644 python/test-data-2.0/nosig_ok.err create mode 100644 python/test-data-2.0/nosig_ok.out create mode 100644 python/test-data-2.0/nosig_ok.py create mode 100644 python/test-data-2.0/testCallable.err create mode 100644 python/test-data-2.0/testCallable.out create mode 100644 python/test-data-2.0/testCallable.py create mode 100644 python/tests/test_renderTy.py diff --git a/python/src/errors.py b/python/src/errors.py index 0e605ed6..912e911c 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -4,7 +4,7 @@ import inspect import location import i18n -from myTypeguard import renderTy +from renderTy import renderTy class WyppError(abc.ABC): def __init__(self, extraFrames: list[inspect.FrameInfo] = []): diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py index 677478fb..aa14556c 100644 --- a/python/src/myTypeguard.py +++ b/python/src/myTypeguard.py @@ -1,10 +1,10 @@ # Wrapper module for typeguard. Do not import typeguard directly but always via myTypeguard from __future__ import annotations from typing import * +from dataclasses import dataclass + # We externally adjust the PYTHONPATH so that the typeguard module can be resolved import typeguard # type: ignore -from dataclasses import dataclass -import sys @dataclass(frozen=True) class Namespaces: @@ -24,8 +24,5 @@ def matchesTy(a: Any, ty: Any, ns: Namespaces) -> bool: except typeguard.TypeCheckError as e: return False -def renderTy(t: Any) -> str: - if isinstance(t, str): - return t +def getTypeName(t: Any) -> str: return typeguard._utils.get_type_name(t, ['__wypp__']) - diff --git a/python/test-data-2.0/nested.err b/python/test-data-2.0/nested.err new file mode 100644 index 00000000..bc736773 --- /dev/null +++ b/python/test-data-2.0/nested.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 nested.py +## Fehlerhafter Aufruf in Zeile 4: + +foo(42) + +## Typ deklariert in Zeile 1: + +def foo(l: list[list[int]]) -> int: diff --git a/python/test-data-2.0/nested.out b/python/test-data-2.0/nested.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/nested.py b/python/test-data-2.0/nested.py new file mode 100644 index 00000000..7287f825 --- /dev/null +++ b/python/test-data-2.0/nested.py @@ -0,0 +1,4 @@ +def foo(l: list[list[int]]) -> int: + return len(l) + +foo(42) diff --git a/python/test-data-2.0/nosig_ok.err b/python/test-data-2.0/nosig_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/nosig_ok.out b/python/test-data-2.0/nosig_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/nosig_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/nosig_ok.py b/python/test-data-2.0/nosig_ok.py new file mode 100644 index 00000000..3b934537 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/testCallable.err b/python/test-data-2.0/testCallable.err new file mode 100644 index 00000000..ef6b5a1d --- /dev/null +++ b/python/test-data-2.0/testCallable.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 testCallable.py +## Fehlerhafter Aufruf in Zeile 6: + +foo(42) + +## Typ deklariert in Zeile 3: + +def foo(f: Callable[[int, bool], str]) -> int: diff --git a/python/test-data-2.0/testCallable.out b/python/test-data-2.0/testCallable.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/testCallable.py b/python/test-data-2.0/testCallable.py new file mode 100644 index 00000000..a5776112 --- /dev/null +++ b/python/test-data-2.0/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/tests/test_renderTy.py b/python/tests/test_renderTy.py new file mode 100644 index 00000000..0b6f4c78 --- /dev/null +++ b/python/tests/test_renderTy.py @@ -0,0 +1,75 @@ +import unittest +from typing import * +from renderTy import renderTy + +class MyClass: + pass + +class TestRenderTy(unittest.TestCase): + + def test_basics(self): + self.assertEqual(renderTy(int), 'int') + self.assertEqual(renderTy(list[str]), 'list[str]') + self.assertEqual(renderTy(dict[str, list[int]]), 'dict[str, list[int]]') + self.assertEqual(renderTy(Optional[dict[str, int]]), 'Optional[dict[str, int]]') + self.assertEqual(renderTy(tuple[int, ...]), 'tuple[int, ...]') + self.assertEqual(renderTy(Union[int, str, bool]), 'Union[int, str, bool]') + self.assertEqual(renderTy(Callable[[int, str, list[float]], bool]), + 'Callable[[int, str, list[float]], bool]') + + def test_advanced_types(self): + # Nested generics + self.assertEqual(renderTy(dict[str, list[tuple[int, str]]]), 'dict[str, list[tuple[int, str]]]') + self.assertEqual(renderTy(list[dict[str, Optional[int]]]), 'list[dict[str, Optional[int]]]') + + # Complex Union types + self.assertEqual(renderTy(Union[int, str, None]), 'Union[int, str, None]') + self.assertEqual(renderTy(Union[list[int], dict[str, int], tuple[int, ...]]), + 'Union[list[int], dict[str, int], tuple[int, ...]]') + self.assertEqual(renderTy(int | str), 'Union[int, str]') + self.assertEqual(renderTy(list[int] | dict[str, int] | None), + 'Union[list[int], dict[str, int], None]') + + # Callable with complex signatures + self.assertEqual(renderTy(Callable[[dict[str, int], Optional[list[str]]], Union[int, str]]), + 'Callable[[dict[str, int], Optional[list[str]]], Union[int, str]]') + self.assertEqual(renderTy(Callable[[], None]), 'Callable[[], None]') + + # Literal types + self.assertEqual(renderTy(Literal['red', 'green', 'blue']), "Literal['red', 'green', 'blue']") + self.assertEqual(renderTy(Literal[1, 2, 3]), 'Literal[1, 2, 3]') + + def test_special_forms(self): + # Any and NoReturn + self.assertEqual(renderTy(Any), 'Any') + self.assertEqual(renderTy(NoReturn), 'NoReturn') + + # Forward references (if supported) + self.assertEqual(renderTy(ForwardRef('SomeClass')), 'SomeClass') + + + def test_edge_cases(self): + # Empty tuple + self.assertEqual(renderTy(tuple[()]), 'tuple[()]') + + # Single element tuple + self.assertEqual(renderTy(tuple[int]), 'tuple[int]') + + # Very nested types + self.assertEqual(renderTy(dict[str, dict[str, dict[str, int]]]), + 'dict[str, dict[str, dict[str, int]]]') + + # Union with single type + self.assertEqual(renderTy(Union[int]), 'int') + + # Complex Callable with nested types + self.assertEqual(renderTy(Callable[[Callable[[int], str], list[int]], dict[str, Any]]), + 'Callable[[Callable[[int], str], list[int]], dict[str, Any]]') + + def test_custom_classes(self): + # Custom class types + + self.assertEqual(renderTy(MyClass), 'tests.test_renderTy.MyClass') + self.assertEqual(renderTy(list[MyClass]), 'list[tests.test_renderTy.MyClass]') + self.assertEqual(renderTy(Optional[MyClass]), 'Optional[tests.test_renderTy.MyClass]') + From c27cc14b25b0196bad30096a25e9ef4987237eca Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 10:42:49 +0200 Subject: [PATCH 34/56] support for detecting illegal types such as list(int) and mutable record attrs --- python/src/errors.py | 38 ++++++++++++++++++++++++ python/src/i18n.py | 20 ++++++++++++- python/src/myTypeguard.py | 12 +++++++- python/src/typecheck.py | 25 ++++++++++++---- python/src/writeYourProgram.py | 45 ++++++++++++----------------- python/test-data-2.0/mutable.err | 17 +++++++++++ python/test-data-2.0/mutable.err_en | 17 +++++++++++ python/test-data-2.0/mutable.out | 0 python/test-data-2.0/mutable.out_en | 0 python/test-data-2.0/mutable.py | 10 +++++++ python/test-data-2.0/mutable_ok.err | 0 python/test-data-2.0/mutable_ok.out | 1 + python/test-data-2.0/mutable_ok.py | 10 +++++++ 13 files changed, 161 insertions(+), 34 deletions(-) create mode 100644 python/test-data-2.0/mutable.err create mode 100644 python/test-data-2.0/mutable.err_en create mode 100644 python/test-data-2.0/mutable.out create mode 100644 python/test-data-2.0/mutable.out_en create mode 100644 python/test-data-2.0/mutable.py create mode 100644 python/test-data-2.0/mutable_ok.err create mode 100644 python/test-data-2.0/mutable_ok.out create mode 100644 python/test-data-2.0/mutable_ok.py diff --git a/python/src/errors.py b/python/src/errors.py index 912e911c..5dfca558 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -55,6 +55,17 @@ def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []): def __str__(self): return f'WyppTypeError: {self.msg}' + @staticmethod + def invalidType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError: + lines = [] + lines.append(i18n.invalidTy(ty)) + 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 resultError(callableName: location.CallableName, resultTypeLoc: Optional[location.Loc], resultTy: Any, returnLoc: Optional[location.Loc], givenValue: Any, @@ -143,5 +154,32 @@ def partialAnnotationError(callableName: location.CallableName, paramName: str, 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: + lines.append('') + lines.append(f'## {i18n.tr("File")} {setterLoc.filename}') + lines.append(f'## {i18n.tr("Problematic assignment in line")} {setterLoc.startLine}:\n') + lines.append(renderLoc(setterLoc)) + if 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(renderLoc(attrLoc)) + raise WyppTypeError('\n'.join(lines)) + class WyppAttributeError(AttributeError, WyppError): pass diff --git a/python/src/i18n.py b/python/src/i18n.py index 54931551..30be991a 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -96,7 +96,15 @@ def tr(key: str, **kws) -> str: 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe.', 'Attribute `{name}` of record `{record}` required a type annotation.': - 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation.' + 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation.', + + 'invalid type `{ty}`': + 'ungültiger Type `{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}`.' } def expectingNoReturn(cn: location.CallableName) -> str: @@ -178,6 +186,9 @@ def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int) -> str: def realArgumentTy(ty: str) -> str: return tr('But the value given 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': @@ -194,3 +205,10 @@ def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str: 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 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) diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py index aa14556c..c8b230a5 100644 --- a/python/src/myTypeguard.py +++ b/python/src/myTypeguard.py @@ -14,7 +14,15 @@ class Namespaces: def empty() -> Namespaces: return Namespaces({}, {}) -def matchesTy(a: Any, ty: Any, ns: Namespaces) -> bool: +@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, @@ -23,6 +31,8 @@ def matchesTy(a: Any, ty: Any, ns: Namespaces) -> bool: return True except typeguard.TypeCheckError as e: return False + except Exception as e: + return MatchesTyFailure(e, ty) def getTypeName(t: Any) -> str: return typeguard._utils.get_type_name(t, ['__wypp__']) diff --git a/python/src/typecheck.py b/python/src/typecheck.py index 87f38ebb..f12ab0f9 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -6,7 +6,7 @@ import typing from dataclasses import dataclass import utils -from myTypeguard import matchesTy, Namespaces +from myTypeguard import matchesTy, MatchesTyResult, MatchesTyFailure, Namespaces import stacktrace import location import errors @@ -25,6 +25,19 @@ def isEmptySignature(sig: inspect.Signature) -> bool: return False return isEmptyAnnotation(sig.return_annotation) +def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool: + match res: + case MatchesTyFailure(exc, ty): + # We want to detect errors such as writing list(int) instead of list[int]. + # Below is a heuristic... + s = str(ty) + if '(' in s: + raise errors.WyppTypeError.invalidType(ty, tyLoc) + else: + raise exc + case b: + return b + def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, code: location.CallableInfo, cfg: CheckCfg) -> None: params = list(sig.parameters) @@ -53,13 +66,13 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(code), name, locDecl) else: a = args[i] - if not matchesTy(a, t, cfg.ns): + locDecl = code.getParamSourceLocation(name) + if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl): fi = stacktrace.callerOutsideWypp() if fi is not None: locArg = location.locationOfArgument(fi, i) else: locArg = None - locDecl = code.getParamSourceLocation(name) raise errors.WyppTypeError.argumentError(location.CallableName.mk(code), name, i - offset, @@ -73,11 +86,11 @@ def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo] t = sig.return_annotation if isEmptyAnnotation(t): t = None - if not matchesTy(result, t, cfg.ns): + locDecl = code.getResultTypeLocation() + if not handleMatchesTyResult(matchesTy(result, t, cfg.ns), locDecl): fi = stacktrace.callerOutsideWypp() if fi is not None: locRes = location.Loc.fromFrameInfo(fi) - locDecl = code.getResultTypeLocation() returnLoc = None extraFrames = [] if returnFrame: @@ -87,7 +100,7 @@ def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo] locRes, extraFrames) -@dataclass(frozen=True) +@dataclass class CheckCfg: kind: location.CallableKind ns: Namespaces diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index db48ca88..bc8666ed 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -5,6 +5,9 @@ import myTypeguard import errors import typecheck +import location +import stacktrace +from utils import _call_with_frames_removed _DEBUG = False def _debug(s): @@ -62,33 +65,12 @@ def _literalInstanceOf(self, value): return True return False -def _invalidCall(self, *args, **kwds): - if hasattr(self, '__name__'): - name = self.__name__ - else: - name = str(self) - typingPrefix = 'typing.' - if name.startswith(typingPrefix): - name = name[len(typingPrefix):] - def formatArg(x): - if name == 'Literal': - return repr(x) - else: - return x - argStr = ', '.join([formatArg(myTypeguard.renderTy(x)) for x in args]) - raise errors.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: isinstance(x, L) # pyright does not know about typing._LiteralGenericAlias, we do not typecheck the following line. setattr(typing._LiteralGenericAlias, '__instancecheck__', _literalInstanceOf) # type: ignore -# 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(): @@ -117,16 +99,27 @@ def _patchDataClass(cls, mutable: bool): # prevent new fields being added fields = set(fieldNames) ns = _getNamespacesOfClass(cls) - + code = location.RecordConstructorInfo(cls) checker = {} # Note: Partial annotations are disallowed, so no handling in this code is required. for name in fields: if name in cls.__annotations__: def check(v): ty = typing.get_type_hints(cls, include_extras=True)[name] - if not myTypeguard.matchesTy(v, ty, ns): - raise TypeError(f'Expected argument of type {myTypeguard.renderTy(ty)} ' \ - f'for attribute {name}, got {myTypeguard.renderTy(type(v))}: {v}') + tyLoc = code.getParamSourceLocation(name) + if not typecheck.handleMatchesTyResult(myTypeguard.matchesTy(v, ty, ns), tyLoc): + fi = stacktrace.callerOutsideWypp() + if fi: + loc = location.Loc.fromFrameInfo(fi) + else: + loc = None + # FIXME: i18n + raise errors.WyppTypeError.recordAssignError(cls.__name__, + name, + ty, + tyLoc, + v, + loc) return v checker[name] = check else: @@ -141,7 +134,7 @@ def _setattr(obj, k, v): oldSetattr(obj, k, v) else: raise errors.WyppAttributeError(f'Unknown attribute {k} for record {cls.__name__}') - setattr(cls, "__setattr__", _setattr) + setattr(cls, "__setattr__", lambda obj, k, v: _call_with_frames_removed(_setattr, obj, k, v)) return cls @typing.dataclass_transform() diff --git a/python/test-data-2.0/mutable.err b/python/test-data-2.0/mutable.err new file mode 100644 index 00000000..0e0f5db9 --- /dev/null +++ b/python/test-data-2.0/mutable.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 10, in + p.y = "foo" + +WyppTypeError: "foo" + +Attribut `x` des Records `Point` deklariert als Typ `int`. +Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. + +## Datei mutable.py +## Fehlerhafte Zuweisung in Zeile 10: + +p.y = "foo" + +## Typ deklariert in Zeile 5: + + x: int diff --git a/python/test-data-2.0/mutable.err_en b/python/test-data-2.0/mutable.err_en new file mode 100644 index 00000000..7f032e99 --- /dev/null +++ b/python/test-data-2.0/mutable.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 10, in + p.y = "foo" + +WyppTypeError: "foo" + +Attribute `x` of record `Point` declared with type `int.` +Cannot set attribute to value of type `str`. + +## File mutable.py +## Problematic assignment in line 10: + +p.y = "foo" + +## Type declared in line 5: + + x: int diff --git a/python/test-data-2.0/mutable.out b/python/test-data-2.0/mutable.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable.out_en b/python/test-data-2.0/mutable.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable.py b/python/test-data-2.0/mutable.py new file mode 100644 index 00000000..fdb88c67 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/mutable_ok.err b/python/test-data-2.0/mutable_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable_ok.out b/python/test-data-2.0/mutable_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/mutable_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/mutable_ok.py b/python/test-data-2.0/mutable_ok.py new file mode 100644 index 00000000..a1eb6aee --- /dev/null +++ b/python/test-data-2.0/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') From 053c6bb6666214dde36f2129292c3de20dc0deed Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 11:18:17 +0200 Subject: [PATCH 35/56] fixed tests --- python/src/writeYourProgram.py | 94 ++-------------------------- python/test-data-2.0/mutable.err | 6 +- python/test-data-2.0/mutable.err_en | 6 +- python/test-data-2.0/mutable2.err | 17 +++++ python/test-data-2.0/mutable2.err_en | 17 +++++ python/test-data-2.0/mutable2.out | 0 python/test-data-2.0/mutable2.out_en | 0 python/test-data-2.0/mutable2.py | 8 +++ python/test-data-2.0/mutable2_ok.err | 0 python/test-data-2.0/mutable2_ok.out | 1 + python/test-data-2.0/mutable2_ok.py | 10 +++ 11 files changed, 64 insertions(+), 95 deletions(-) create mode 100644 python/test-data-2.0/mutable2.err create mode 100644 python/test-data-2.0/mutable2.err_en create mode 100644 python/test-data-2.0/mutable2.out create mode 100644 python/test-data-2.0/mutable2.out_en create mode 100644 python/test-data-2.0/mutable2.py create mode 100644 python/test-data-2.0/mutable2_ok.err create mode 100644 python/test-data-2.0/mutable2_ok.out create mode 100644 python/test-data-2.0/mutable2_ok.py diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index bc8666ed..dc504af9 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -1,13 +1,10 @@ import typing import dataclasses import inspect -import sys -import myTypeguard import errors import typecheck -import location -import stacktrace from utils import _call_with_frames_removed +import records _DEBUG = False def _debug(s): @@ -31,6 +28,7 @@ def _debug(s): Callable = typing.Callable dataclass = dataclasses.dataclass +record = records.record intPositive = typing.Annotated[int, lambda i: i > 0, 'intPositive'] nat = typing.Annotated[int, lambda i: i >= 0, 'nat'] @@ -71,88 +69,6 @@ def _literalInstanceOf(self, value): # pyright does not know about typing._LiteralGenericAlias, we do not typecheck the following line. setattr(typing._LiteralGenericAlias, '__instancecheck__', _literalInstanceOf) # type: ignore -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 _getNamespacesOfClass(cls): - mod = sys.modules.get(cls.__module__) or inspect.getmodule(cls) - globals = vars(mod) if mod else {} - owner = getattr(cls, "__qualname__", "").split(".")[0] - locals = globals.get(owner, {}) - return myTypeguard.Namespaces(globals, locals) - -def _patchDataClass(cls, mutable: bool): - 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) - - if mutable: - # prevent new fields being added - fields = set(fieldNames) - ns = _getNamespacesOfClass(cls) - code = location.RecordConstructorInfo(cls) - checker = {} - # Note: Partial annotations are disallowed, so no handling in this code is required. - for name in fields: - if name in cls.__annotations__: - def check(v): - ty = typing.get_type_hints(cls, include_extras=True)[name] - tyLoc = code.getParamSourceLocation(name) - if not typecheck.handleMatchesTyResult(myTypeguard.matchesTy(v, ty, ns), tyLoc): - fi = stacktrace.callerOutsideWypp() - if fi: - loc = location.Loc.fromFrameInfo(fi) - else: - loc = None - # FIXME: i18n - raise errors.WyppTypeError.recordAssignError(cls.__name__, - name, - ty, - tyLoc, - v, - loc) - return v - checker[name] = check - else: - raise errors.WyppTypeError.noTypeAnnotationForRecordAttribute(name, cls.__name__) - - 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) - else: - raise errors.WyppAttributeError(f'Unknown attribute {k} for record {cls.__name__}') - setattr(cls, "__setattr__", lambda obj, k, v: _call_with_frames_removed(_setattr, obj, k, v)) - return cls - -@typing.dataclass_transform() -def record(cls=None, mutable=False): - def wrap(cls: type): - newCls = dataclasses.dataclass(cls, frozen=not mutable) - if _typeCheckingEnabled: - return _patchDataClass(newCls, mutable) - 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 wrap(cls) - def typechecked(func=None): def wrap(func): if hasattr(func, '__qualname__'): @@ -191,6 +107,7 @@ def initModule(enableChecks=True, enableTypeChecking=True, quiet=False): global _checksEnabled, _typeCheckingEnabled _checksEnabled = enableChecks _typeCheckingEnabled = enableTypeChecking + records.init(enableTypeChecking) resetTestCount() def resetTestCount(): @@ -314,9 +231,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 @@ -329,7 +246,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) diff --git a/python/test-data-2.0/mutable.err b/python/test-data-2.0/mutable.err index 0e0f5db9..40ec7c04 100644 --- a/python/test-data-2.0/mutable.err +++ b/python/test-data-2.0/mutable.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: "foo" -Attribut `x` des Records `Point` deklariert als Typ `int`. +Attribut `y` des Records `Point` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. ## Datei mutable.py @@ -12,6 +12,6 @@ Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. p.y = "foo" -## Typ deklariert in Zeile 5: +## Typ deklariert in Zeile 6: - x: int + y: int diff --git a/python/test-data-2.0/mutable.err_en b/python/test-data-2.0/mutable.err_en index 7f032e99..840729b6 100644 --- a/python/test-data-2.0/mutable.err_en +++ b/python/test-data-2.0/mutable.err_en @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: "foo" -Attribute `x` of record `Point` declared with type `int.` +Attribute `y` of record `Point` declared with type `int.` Cannot set attribute to value of type `str`. ## File mutable.py @@ -12,6 +12,6 @@ Cannot set attribute to value of type `str`. p.y = "foo" -## Type declared in line 5: +## Type declared in line 6: - x: int + y: int diff --git a/python/test-data-2.0/mutable2.err b/python/test-data-2.0/mutable2.err new file mode 100644 index 00000000..ca503b65 --- /dev/null +++ b/python/test-data-2.0/mutable2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 8, in + p = Point(1, '2') + +WyppTypeError: '2' + +Der Aufruf des Konstruktors der Klasse `Point` erwartet Wert vom Typ `int` als zweites Argument. +Aber der übergebene Wert hat Typ `str`. + +## Datei mutable2.py +## Fehlerhafter Aufruf in Zeile 8: + +p = Point(1, '2') + +## Typ deklariert in Zeile 6: + + y: int diff --git a/python/test-data-2.0/mutable2.err_en b/python/test-data-2.0/mutable2.err_en new file mode 100644 index 00000000..3cc36974 --- /dev/null +++ b/python/test-data-2.0/mutable2.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", line 8, in + p = Point(1, '2') + +WyppTypeError: '2' + +The call of the constructor of class `Point` expects value of type `int` as 2nd argument. +But the value given has type `str`. + +## File mutable2.py +## Problematic call in line 8: + +p = Point(1, '2') + +## Type declared in line 6: + + y: int diff --git a/python/test-data-2.0/mutable2.out b/python/test-data-2.0/mutable2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable2.out_en b/python/test-data-2.0/mutable2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable2.py b/python/test-data-2.0/mutable2.py new file mode 100644 index 00000000..8e5f2161 --- /dev/null +++ b/python/test-data-2.0/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-2.0/mutable2_ok.err b/python/test-data-2.0/mutable2_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/mutable2_ok.out b/python/test-data-2.0/mutable2_ok.out new file mode 100644 index 00000000..337e5e81 --- /dev/null +++ b/python/test-data-2.0/mutable2_ok.out @@ -0,0 +1 @@ +CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=[]) diff --git a/python/test-data-2.0/mutable2_ok.py b/python/test-data-2.0/mutable2_ok.py new file mode 100644 index 00000000..5300f178 --- /dev/null +++ b/python/test-data-2.0/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) From 0c8f0b5544a9fdac1505c3eef9da579c20f6a363 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 11:18:39 +0200 Subject: [PATCH 36/56] add missing files --- python/src/records.py | 104 +++++++++++++++++++++++++++++++++++++++++ python/src/renderTy.py | 77 ++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 python/src/records.py create mode 100644 python/src/renderTy.py diff --git a/python/src/records.py b/python/src/records.py new file mode 100644 index 00000000..11378909 --- /dev/null +++ b/python/src/records.py @@ -0,0 +1,104 @@ +import typing +import dataclasses +import inspect +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 _getNamespacesOfClass(cls): + mod = sys.modules.get(cls.__module__) or inspect.getmodule(cls) + globals = vars(mod) if mod else {} + owner = getattr(cls, "__qualname__", "").split(".")[0] + locals = globals.get(owner, {}) + return myTypeguard.Namespaces(globals, locals) + +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 + # FIXME: i18n + raise errors.WyppTypeError.recordAssignError(cls.__name__, + name, + ty, + tyLoc, + v, + loc) + return v + +def _patchDataClass(cls, mutable: bool): + 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) + + if mutable: + # prevent new fields being added + fields = set(fieldNames) + ns = _getNamespacesOfClass(cls) + info = location.RecordConstructorInfo(cls) + types = typing.get_type_hints(cls, include_extras=True) + locs = {} + for name in fields: + if not name in cls.__annotations__: + raise errors.WyppTypeError.noTypeAnnotationForRecordAttribute(name, cls.__name__) + else: + locs[name] = info.getParamSourceLocation(name) + + oldSetattr = cls.__setattr__ + def _setattr(obj, name, v): + ty = types[name] + tyLoc = locs[name] + v = _checkRecordAttr(cls, ns, name, ty, tyLoc, v) + if name in fields: + 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 + +@typing.dataclass_transform() +def record(cls=None, mutable=False): + def wrap(cls: type): + newCls = dataclasses.dataclass(cls, frozen=not mutable) + if _typeCheckingEnabled: + return _patchDataClass(newCls, mutable) + 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 wrap(cls) diff --git a/python/src/renderTy.py b/python/src/renderTy.py new file mode 100644 index 00000000..db08bf1f --- /dev/null +++ b/python/src/renderTy.py @@ -0,0 +1,77 @@ +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 "..." + 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 + 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)) + return f"{name}[" + ", ".join(renderTy(a) for a in args) + "]" From eff4f7486a717500fd698d37e209f689dd03e0b8 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 14:45:57 +0200 Subject: [PATCH 37/56] fixes --- python/fileTestsLib.py | 16 ++- python/src/errors.py | 46 +++++++- python/src/i18n.py | 138 +++++++++++++++++++--- python/src/location.py | 4 +- python/src/records.py | 10 +- python/src/stacktrace.py | 1 - python/src/typecheck.py | 87 ++++++++++---- python/src/utils.py | 16 +++ python/test-data-2.0/optionalArgs.err | 13 ++ python/test-data-2.0/optionalArgs.err_en | 13 ++ python/test-data-2.0/optionalArgs.out | 0 python/test-data-2.0/optionalArgs.out_en | 0 python/test-data-2.0/optionalArgs.py | 4 + python/test-data-2.0/optionalArgs2.err | 13 ++ python/test-data-2.0/optionalArgs2.err_en | 13 ++ python/test-data-2.0/optionalArgs2.out | 0 python/test-data-2.0/optionalArgs2.out_en | 0 python/test-data-2.0/optionalArgs2.py | 4 + python/test-data-2.0/optionalArgs3.err | 13 ++ python/test-data-2.0/optionalArgs3.err_en | 13 ++ python/test-data-2.0/optionalArgs3.out | 0 python/test-data-2.0/optionalArgs3.out_en | 0 python/test-data-2.0/optionalArgs3.py | 4 + python/test-data-2.0/optionalArgs4.err | 15 +++ python/test-data-2.0/optionalArgs4.err_en | 15 +++ python/test-data-2.0/optionalArgs4.out | 0 python/test-data-2.0/optionalArgs4.out_en | 0 python/test-data-2.0/optionalArgs4.py | 5 + python/test-data-2.0/optionalArgs_ok.err | 0 python/test-data-2.0/optionalArgs_ok.out | 1 + python/test-data-2.0/optionalArgs_ok.py | 5 + python/test-data-2.0/optionalAttr.err | 13 ++ python/test-data-2.0/optionalAttr.err_en | 13 ++ python/test-data-2.0/optionalAttr.out | 0 python/test-data-2.0/optionalAttr.out_en | 0 python/test-data-2.0/optionalAttr.py | 6 + python/test-data-2.0/optionalAttr2.err | 13 ++ python/test-data-2.0/optionalAttr2.err_en | 13 ++ python/test-data-2.0/optionalAttr2.out | 0 python/test-data-2.0/optionalAttr2.out_en | 0 python/test-data-2.0/optionalAttr2.py | 9 ++ python/test-data-2.0/optionalAttr3.err | 13 ++ python/test-data-2.0/optionalAttr3.err_en | 13 ++ python/test-data-2.0/optionalAttr3.out | 0 python/test-data-2.0/optionalAttr3.out_en | 0 python/test-data-2.0/optionalAttr3.py | 9 ++ python/test-data-2.0/optionalAttr_ok.err | 0 python/test-data-2.0/optionalAttr_ok.out | 1 + python/test-data-2.0/optionalAttr_ok.py | 10 ++ python/test-data-2.0/partial.err | 6 +- python/test-data-2.0/partial.err_en | 4 +- python/test-data-2.0/tooFewArgs.err | 13 ++ python/test-data-2.0/tooFewArgs.out | 0 python/test-data-2.0/tooFewArgs.py | 4 + python/test-data-2.0/tooFewAttrs.err | 13 ++ python/test-data-2.0/tooFewAttrs.out | 0 python/test-data-2.0/tooFewAttrs.py | 8 ++ python/test-data-2.0/tooManyArgs.err | 13 ++ python/test-data-2.0/tooManyArgs.err_en | 13 ++ python/test-data-2.0/tooManyArgs.out | 0 python/test-data-2.0/tooManyArgs.out_en | 0 python/test-data-2.0/tooManyArgs.py | 4 + python/test-data-2.0/tooManyAttrs.err | 13 ++ python/test-data-2.0/tooManyAttrs.err_en | 13 ++ python/test-data-2.0/tooManyAttrs.out | 0 python/test-data-2.0/tooManyAttrs.out_en | 0 python/test-data-2.0/tooManyAttrs.py | 8 ++ python/tests/test_i18n.py | 26 ++-- 68 files changed, 626 insertions(+), 74 deletions(-) create mode 100644 python/test-data-2.0/optionalArgs.err create mode 100644 python/test-data-2.0/optionalArgs.err_en create mode 100644 python/test-data-2.0/optionalArgs.out create mode 100644 python/test-data-2.0/optionalArgs.out_en create mode 100644 python/test-data-2.0/optionalArgs.py create mode 100644 python/test-data-2.0/optionalArgs2.err create mode 100644 python/test-data-2.0/optionalArgs2.err_en create mode 100644 python/test-data-2.0/optionalArgs2.out create mode 100644 python/test-data-2.0/optionalArgs2.out_en create mode 100644 python/test-data-2.0/optionalArgs2.py create mode 100644 python/test-data-2.0/optionalArgs3.err create mode 100644 python/test-data-2.0/optionalArgs3.err_en create mode 100644 python/test-data-2.0/optionalArgs3.out create mode 100644 python/test-data-2.0/optionalArgs3.out_en create mode 100644 python/test-data-2.0/optionalArgs3.py create mode 100644 python/test-data-2.0/optionalArgs4.err create mode 100644 python/test-data-2.0/optionalArgs4.err_en create mode 100644 python/test-data-2.0/optionalArgs4.out create mode 100644 python/test-data-2.0/optionalArgs4.out_en create mode 100644 python/test-data-2.0/optionalArgs4.py create mode 100644 python/test-data-2.0/optionalArgs_ok.err create mode 100644 python/test-data-2.0/optionalArgs_ok.out create mode 100644 python/test-data-2.0/optionalArgs_ok.py create mode 100644 python/test-data-2.0/optionalAttr.err create mode 100644 python/test-data-2.0/optionalAttr.err_en create mode 100644 python/test-data-2.0/optionalAttr.out create mode 100644 python/test-data-2.0/optionalAttr.out_en create mode 100644 python/test-data-2.0/optionalAttr.py create mode 100644 python/test-data-2.0/optionalAttr2.err create mode 100644 python/test-data-2.0/optionalAttr2.err_en create mode 100644 python/test-data-2.0/optionalAttr2.out create mode 100644 python/test-data-2.0/optionalAttr2.out_en create mode 100644 python/test-data-2.0/optionalAttr2.py create mode 100644 python/test-data-2.0/optionalAttr3.err create mode 100644 python/test-data-2.0/optionalAttr3.err_en create mode 100644 python/test-data-2.0/optionalAttr3.out create mode 100644 python/test-data-2.0/optionalAttr3.out_en create mode 100644 python/test-data-2.0/optionalAttr3.py create mode 100644 python/test-data-2.0/optionalAttr_ok.err create mode 100644 python/test-data-2.0/optionalAttr_ok.out create mode 100644 python/test-data-2.0/optionalAttr_ok.py create mode 100644 python/test-data-2.0/tooFewArgs.err create mode 100644 python/test-data-2.0/tooFewArgs.out create mode 100644 python/test-data-2.0/tooFewArgs.py create mode 100644 python/test-data-2.0/tooFewAttrs.err create mode 100644 python/test-data-2.0/tooFewAttrs.out create mode 100644 python/test-data-2.0/tooFewAttrs.py create mode 100644 python/test-data-2.0/tooManyArgs.err create mode 100644 python/test-data-2.0/tooManyArgs.err_en create mode 100644 python/test-data-2.0/tooManyArgs.out create mode 100644 python/test-data-2.0/tooManyArgs.out_en create mode 100644 python/test-data-2.0/tooManyArgs.py create mode 100644 python/test-data-2.0/tooManyAttrs.err create mode 100644 python/test-data-2.0/tooManyAttrs.err_en create mode 100644 python/test-data-2.0/tooManyAttrs.out create mode 100644 python/test-data-2.0/tooManyAttrs.out_en create mode 100644 python/test-data-2.0/tooManyAttrs.py diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 0bf30dd6..90043f82 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -8,6 +8,8 @@ import re import shutil +GLOBAL_CHECK_OUTPUTS = True + @dataclass(frozen=True) class TestOpts: cmd: str @@ -58,7 +60,7 @@ class TestResults: passed: list[str] failed: list[str] skipped: list[str] - def record(self, testFail: str, result: TestStatus): + def storeTestResult(self, testFail: str, result: TestStatus): if result == 'passed': self.passed.append(testFail) elif result == 'failed': @@ -157,7 +159,7 @@ def checkOutputOk(testFile: str, outputType: str, expectedFile: str, actualFile: def checkInstall(testFile: str, ctx: TestContext=globalCtx): if shouldSkip(testFile, ctx, None): - ctx.results.record(testFile, 'skipped') + ctx.results.storeTestResult(testFile, 'skipped') return with tempfile.TemporaryDirectory() as d: def run(args: list[str]): @@ -256,14 +258,16 @@ def _checkForLang(testFile: str, with tempfile.TemporaryDirectory() as d: actualStdoutFile = os.path.join(d, 'stdout.txt') actualStderrFile = os.path.join(d, 'stderr.txt') - _runTest(testFile, exitCode, typecheck, args, actualStdoutFile, actualStderrFile, - pythonPath, what, lang, ctx) + 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: + if checkOutputs and GLOBAL_CHECK_OUTPUTS: if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile): return 'failed' if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile): @@ -317,7 +321,7 @@ def check(testFile: str, if guessExitCode(testFile) == 0: exitCode = 0 status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, checkOutputs, ctx, what) - ctx.results.record(testFile, status) + ctx.results.storeTestResult(testFile, status) if status == 'failed': if not ctx.opts.keepGoing: ctx.results.finish() diff --git a/python/src/errors.py b/python/src/errors.py index 5dfca558..31580fc9 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -139,6 +139,47 @@ def argumentError(callableName: location.CallableName, paramName: str, paramInde lines.append(renderLoc(paramLoc)) 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: + lines.append('') + lines.append(f'## {i18n.tr("File")} {paramLoc.filename}') + lines.append(f'## {i18n.tr("Parameter declared in line")} {paramLoc.startLine}:\n') + lines.append(renderLoc(paramLoc)) + 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: + lines.append('') + lines.append(f'## {i18n.tr("File")} {callLoc.filename}') + lines.append(f'## {i18n.tr("Call in line")} {callLoc.startLine}:\n') + lines.append(renderLoc(callLoc)) + raise WyppTypeError('\n'.join(lines)) + @staticmethod def partialAnnotationError(callableName: location.CallableName, paramName: str, paramLoc: Optional[location.Loc]) -> WyppTypeError: lines = [] @@ -182,4 +223,7 @@ def recordAssignError(recordName: str, raise WyppTypeError('\n'.join(lines)) class WyppAttributeError(AttributeError, WyppError): - pass + @staticmethod + def unknownAttr(clsName: str, attrName: str) -> WyppAttributeError: + return WyppAttributeError(i18n.tr('Unknown attribute {attrName} for record {clsName}', + clsName=clsName, attrName=attrName)) diff --git a/python/src/i18n.py b/python/src/i18n.py index 30be991a..1070e52c 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -51,15 +51,15 @@ def tr(key: str, **kws) -> str: '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 class `{cls}`.': - 'Kein Rückgabewert erwartet bei Aufruf des Konstruktors der 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 class `{cls}`.': - 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf des Konstruktors der 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.', @@ -74,10 +74,11 @@ def tr(key: str, **kws) -> str: '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 class `{cls}` expects value of type `{ty}` as {arg}.': - 'Der Aufruf des Konstruktors der 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', 'Result type declared in line': 'Rückgabetyp deklariert in Zeile', @@ -89,11 +90,11 @@ def tr(key: str, **kws) -> str: '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 Typangabe.', + '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 Typangabe.', - 'Parameter `{param}` of the constructor of class `{cls}` requires a type annotation.': - 'Parameter `{param}` des Konstruktors der Klasse `{cls}` benötigt eine Typangabe.', + '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.', @@ -104,7 +105,31 @@ def tr(key: str, **kws) -> str: '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}`.' + '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}', + } def expectingNoReturn(cn: location.CallableName) -> str: @@ -115,8 +140,8 @@ def expectingNoReturn(cn: location.CallableName) -> str: 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('constructor', cls): - return tr('Expecting no return value when calling constructor of class `{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}') @@ -137,8 +162,8 @@ def expectingReturnOfType(cn: location.CallableName, ty: str) -> str: 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('constructor', cls): - return tr('Expecting return value of type `{ty}` when calling constructor of class `{cls}`.', + 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}') @@ -178,14 +203,89 @@ def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int) -> str: 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('constructor', cls): - return tr('The call of the constructor of class `{cls}` expects value of type `{ty}` as {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) @@ -197,8 +297,8 @@ def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str: 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('constructor', cls): - return tr('Parameter `{param}` of the constructor of class `{cls}` requires a type annotation.', + 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}') diff --git a/python/src/location.py b/python/src/location.py index 07287386..40b15dcd 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -108,7 +108,7 @@ def highlightedLines(loc: Loc) -> list[SourceLine]: @dataclass class ClassMember: - kind: Literal['method', 'constructor'] + kind: Literal['method', 'recordConstructor'] className: str type CallableKind = Literal['function'] | ClassMember @@ -221,7 +221,7 @@ class RecordConstructorInfo(CallableInfo): Class giving access to various properties of a record constructor. """ def __init__(self, cls: type): - super().__init__(ClassMember('constructor', cls.__name__)) + super().__init__(ClassMember('recordConstructor', cls.__name__)) self.__cls = cls @property def name(self): diff --git a/python/src/records.py b/python/src/records.py index 11378909..ead2e8da 100644 --- a/python/src/records.py +++ b/python/src/records.py @@ -77,10 +77,10 @@ def _patchDataClass(cls, mutable: bool): oldSetattr = cls.__setattr__ def _setattr(obj, name, v): - ty = types[name] - tyLoc = locs[name] - v = _checkRecordAttr(cls, ns, name, ty, tyLoc, v) - if name in fields: + 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__}') @@ -101,4 +101,4 @@ def wrap(cls: type): return wrap else: # We're called as @dataclass without parens. - return wrap(cls) + return _call_with_frames_removed(wrap, cls) diff --git a/python/src/stacktrace.py b/python/src/stacktrace.py index e862d7d3..ea7dcc9e 100644 --- a/python/src/stacktrace.py +++ b/python/src/stacktrace.py @@ -38,7 +38,6 @@ def limitTraceback(frameList: list[types.FrameType], for i in range(endIdx - 1, 0, -1): if isCallWithFramesRemoved(frameList[i]): endIdx = i - 1 - break frameList = utils.dropWhile(frameList[:endIdx], lambda f: isWyppFrame(f) or isRunpyFrame(f)) frameList = frameList + [f.frame for f in extraFrames] frames = [(f, f.f_lineno) for f in frameList] diff --git a/python/src/typecheck.py b/python/src/typecheck.py index f12ab0f9..afbea0c3 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -38,42 +38,82 @@ def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) - case b: return b -def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, - code: location.CallableInfo, cfg: CheckCfg) -> None: - params = list(sig.parameters) - if len(params) != len(args): - raise TypeError(f"Expected {len(params)} arguments, got {len(args)}") +def getKind(cfg: CheckCfg, paramNames: list[str]) -> Literal['function', 'method', 'staticmethod']: kind: Literal['function', 'method', 'staticmethod'] = 'function' match cfg.kind: - case location.ClassMember('constructor', _): + case location.ClassMember('recordConstructor', _): kind = 'method' case location.ClassMember('method', _): - if len(params) > 0 and params[0] == 'self': + 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 checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, + info: location.CallableInfo, cfg: CheckCfg) -> None: + paramNames = list(sig.parameters) + mandatory = mandatoryArgCount(sig) + kind = getKind(cfg, paramNames) offset = 1 if kind == 'method' else 0 + cn = location.CallableName.mk(info) + fi = stacktrace.callerOutsideWypp() + def raiseArgMismatch(): + callLoc = None if not fi else location.Loc.fromFrameInfo(fi) + raise errors.WyppTypeError.argCountMismatch(cn, + callLoc, + len(paramNames) - offset, + mandatory - offset, + len(args) - offset) + if len(args) < mandatory: + raiseArgMismatch() for i in range(len(args)): - name = params[i] + if i >= len(paramNames): + raiseArgMismatch() + name = paramNames[i] p = sig.parameters[name] t = p.annotation - if i == 0 and kind == 'method' and isEmptyAnnotation(t): - pass - elif i != 0 and isEmptyAnnotation(t): - locDecl = code.getParamSourceLocation(name) - raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(code), name, locDecl) - else: + if not isEmptyAnnotation(t): a = args[i] - locDecl = code.getParamSourceLocation(name) + locDecl = info.getParamSourceLocation(name) if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl): - fi = stacktrace.callerOutsideWypp() if fi is not None: locArg = location.locationOfArgument(fi, i) else: locArg = None - raise errors.WyppTypeError.argumentError(location.CallableName.mk(code), + raise errors.WyppTypeError.argumentError(cn, name, i - offset, locDecl, @@ -126,23 +166,24 @@ def getNamespacesOfCallable(func: Callable): locals = globals.get(owner, {}) return Namespaces(globals, locals) -def wrapTypecheck(cfg: dict, outerCode: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: +def wrapTypecheck(cfg: dict, outerInfo: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: outerCheckCfg = CheckCfg.fromDict(cfg) def _wrap(f: Callable[P, T]) -> Callable[P, T]: sig = inspect.signature(f) if isEmptySignature(sig): return f checkCfg = outerCheckCfg.setNamespaces(getNamespacesOfCallable(f)) - if outerCode is None: - code = location.StdCallableInfo(f, checkCfg.kind) + if outerInfo is None: + info = location.StdCallableInfo(f, checkCfg.kind) else: - code = outerCode + info = outerInfo + utils._call_with_frames_removed(checkSignature, sig, info, checkCfg) def wrapped(*args, **kwargs) -> T: returnTracker = stacktrace.installProfileHook() - utils._call_with_frames_removed(checkArguments, sig, args, kwargs, code, checkCfg) + utils._call_with_frames_removed(checkArguments, sig, args, kwargs, info, checkCfg) result = f(*args, **kwargs) utils._call_with_frames_removed( - checkReturn, sig, returnTracker.getReturnFrame(), result, code, checkCfg + checkReturn, sig, returnTracker.getReturnFrame(), result, info, checkCfg ) return result return wrapped diff --git a/python/src/utils.py b/python/src/utils.py index 5a2d4343..8dffb1df 100644 --- a/python/src/utils.py +++ b/python/src/utils.py @@ -1,5 +1,6 @@ from typing import * import os +from contextlib import contextmanager P = ParamSpec("P") T = TypeVar("T") @@ -30,3 +31,18 @@ def dropWhile(l: list, f: Callable[[Any], bool]) -> list: 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 diff --git a/python/test-data-2.0/optionalArgs.err b/python/test-data-2.0/optionalArgs.err new file mode 100644 index 00000000..a3dd5ede --- /dev/null +++ b/python/test-data-2.0/optionalArgs.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 optionalArgs.py +## Parameter deklariert in Zeile 1: + +def foo(i: int, s: str=2): diff --git a/python/test-data-2.0/optionalArgs.err_en b/python/test-data-2.0/optionalArgs.err_en new file mode 100644 index 00000000..7939ab76 --- /dev/null +++ b/python/test-data-2.0/optionalArgs.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 optionalArgs.py +## Parameter declared in line 1: + +def foo(i: int, s: str=2): diff --git a/python/test-data-2.0/optionalArgs.out b/python/test-data-2.0/optionalArgs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs.out_en b/python/test-data-2.0/optionalArgs.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs.py b/python/test-data-2.0/optionalArgs.py new file mode 100644 index 00000000..96a209d1 --- /dev/null +++ b/python/test-data-2.0/optionalArgs.py @@ -0,0 +1,4 @@ +def foo(i: int, s: str=2): + pass + +foo(1) diff --git a/python/test-data-2.0/optionalArgs2.err b/python/test-data-2.0/optionalArgs2.err new file mode 100644 index 00000000..5fd81cf1 --- /dev/null +++ b/python/test-data-2.0/optionalArgs2.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + +WyppTypeError: Anzahl der Argument passt nicht + +Funktion `foo` benötigt mindestens 2 Argumente. +Gegeben: 1 Argument + +## Datei optionalArgs2.py +## Aufruf in Zeile 4: + +foo(1) diff --git a/python/test-data-2.0/optionalArgs2.err_en b/python/test-data-2.0/optionalArgs2.err_en new file mode 100644 index 00000000..730f78d5 --- /dev/null +++ b/python/test-data-2.0/optionalArgs2.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + +WyppTypeError: argument count mismatch + +Function `foo` takes at least 2 arguments. +Given: 1 argument + +## File optionalArgs2.py +## Call in line 4: + +foo(1) diff --git a/python/test-data-2.0/optionalArgs2.out b/python/test-data-2.0/optionalArgs2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs2.out_en b/python/test-data-2.0/optionalArgs2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs2.py b/python/test-data-2.0/optionalArgs2.py new file mode 100644 index 00000000..3a145537 --- /dev/null +++ b/python/test-data-2.0/optionalArgs2.py @@ -0,0 +1,4 @@ +def foo(i: int, j: int, s: int=2): + pass + +foo(1) diff --git a/python/test-data-2.0/optionalArgs3.err b/python/test-data-2.0/optionalArgs3.err new file mode 100644 index 00000000..22782f9e --- /dev/null +++ b/python/test-data-2.0/optionalArgs3.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 optionalArgs3.py +## Aufruf in Zeile 4: + +foo(1, 2, 3, 4) diff --git a/python/test-data-2.0/optionalArgs3.err_en b/python/test-data-2.0/optionalArgs3.err_en new file mode 100644 index 00000000..f2756f69 --- /dev/null +++ b/python/test-data-2.0/optionalArgs3.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1, 2, 3, 4) + +WyppTypeError: argument count mismatch + +Function `foo` takes at most 3 arguments. +Given: 4 arguments + +## File optionalArgs3.py +## Call in line 4: + +foo(1, 2, 3, 4) diff --git a/python/test-data-2.0/optionalArgs3.out b/python/test-data-2.0/optionalArgs3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs3.out_en b/python/test-data-2.0/optionalArgs3.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs3.py b/python/test-data-2.0/optionalArgs3.py new file mode 100644 index 00000000..7c9c48d0 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/optionalArgs4.err b/python/test-data-2.0/optionalArgs4.err new file mode 100644 index 00000000..bdcd6df7 --- /dev/null +++ b/python/test-data-2.0/optionalArgs4.err @@ -0,0 +1,15 @@ +Traceback (most recent call last): + File "", line 1, in + class C: + File "", line 1, in C + class C: + +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 optionalArgs4.py +## Parameter deklariert in Zeile 2: + + def __init__(i: int, s: str=2): diff --git a/python/test-data-2.0/optionalArgs4.err_en b/python/test-data-2.0/optionalArgs4.err_en new file mode 100644 index 00000000..0b3d8128 --- /dev/null +++ b/python/test-data-2.0/optionalArgs4.err_en @@ -0,0 +1,15 @@ +Traceback (most recent call last): + File "", line 1, in + class C: + File "", line 1, in C + class C: + +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 optionalArgs4.py +## Parameter declared in line 2: + + def __init__(i: int, s: str=2): diff --git a/python/test-data-2.0/optionalArgs4.out b/python/test-data-2.0/optionalArgs4.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs4.out_en b/python/test-data-2.0/optionalArgs4.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs4.py b/python/test-data-2.0/optionalArgs4.py new file mode 100644 index 00000000..6174209d --- /dev/null +++ b/python/test-data-2.0/optionalArgs4.py @@ -0,0 +1,5 @@ +class C: + def __init__(i: int, s: str=2): + pass + +C(1) diff --git a/python/test-data-2.0/optionalArgs_ok.err b/python/test-data-2.0/optionalArgs_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalArgs_ok.out b/python/test-data-2.0/optionalArgs_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/optionalArgs_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/optionalArgs_ok.py b/python/test-data-2.0/optionalArgs_ok.py new file mode 100644 index 00000000..3abb6630 --- /dev/null +++ b/python/test-data-2.0/optionalArgs_ok.py @@ -0,0 +1,5 @@ +def foo(i: int, s: str='2'): + pass + +foo(1) +print('ok') diff --git a/python/test-data-2.0/optionalAttr.err b/python/test-data-2.0/optionalAttr.err new file mode 100644 index 00000000..0b11ee05 --- /dev/null +++ b/python/test-data-2.0/optionalAttr.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 optionalAttr.py +## Parameter deklariert in Zeile 6: + + y: int = 'foo' diff --git a/python/test-data-2.0/optionalAttr.err_en b/python/test-data-2.0/optionalAttr.err_en new file mode 100644 index 00000000..0c545844 --- /dev/null +++ b/python/test-data-2.0/optionalAttr.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 optionalAttr.py +## Parameter declared in line 6: + + y: int = 'foo' diff --git a/python/test-data-2.0/optionalAttr.out b/python/test-data-2.0/optionalAttr.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr.out_en b/python/test-data-2.0/optionalAttr.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr.py b/python/test-data-2.0/optionalAttr.py new file mode 100644 index 00000000..dbfbae84 --- /dev/null +++ b/python/test-data-2.0/optionalAttr.py @@ -0,0 +1,6 @@ +from wypp import * + +@record +class C: + x: int + y: int = 'foo' diff --git a/python/test-data-2.0/optionalAttr2.err b/python/test-data-2.0/optionalAttr2.err new file mode 100644 index 00000000..c01979f2 --- /dev/null +++ b/python/test-data-2.0/optionalAttr2.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 9, in + c = C(1) + +WyppTypeError: Anzahl der Argument passt nicht + +Konstruktor der Klasse `C` benötigt mindestens 2 Argumente. +Gegeben: 1 Argument + +## Datei optionalAttr2.py +## Aufruf in Zeile 9: + +c = C(1) diff --git a/python/test-data-2.0/optionalAttr2.err_en b/python/test-data-2.0/optionalAttr2.err_en new file mode 100644 index 00000000..8e86d632 --- /dev/null +++ b/python/test-data-2.0/optionalAttr2.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 9, in + c = C(1) + +WyppTypeError: argument count mismatch + +Constructor of class `C` takes at least 2 arguments. +Given: 1 argument + +## File optionalAttr2.py +## Call in line 9: + +c = C(1) diff --git a/python/test-data-2.0/optionalAttr2.out b/python/test-data-2.0/optionalAttr2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr2.out_en b/python/test-data-2.0/optionalAttr2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr2.py b/python/test-data-2.0/optionalAttr2.py new file mode 100644 index 00000000..3eef77fe --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/optionalAttr3.err b/python/test-data-2.0/optionalAttr3.err new file mode 100644 index 00000000..8271acb0 --- /dev/null +++ b/python/test-data-2.0/optionalAttr3.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 9, in + c = C(1, 2, 3, 4) + +WyppTypeError: Anzahl der Argument passt nicht + +Konstruktor der Klasse `C` akzeptiert höchstens 3 Argumente. +Gegeben: 4 Argumente + +## Datei optionalAttr3.py +## Aufruf in Zeile 9: + +c = C(1, 2, 3, 4) diff --git a/python/test-data-2.0/optionalAttr3.err_en b/python/test-data-2.0/optionalAttr3.err_en new file mode 100644 index 00000000..a3c6b21e --- /dev/null +++ b/python/test-data-2.0/optionalAttr3.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 9, in + c = C(1, 2, 3, 4) + +WyppTypeError: argument count mismatch + +Constructor of class `C` takes at most 3 arguments. +Given: 4 arguments + +## File optionalAttr3.py +## Call in line 9: + +c = C(1, 2, 3, 4) diff --git a/python/test-data-2.0/optionalAttr3.out b/python/test-data-2.0/optionalAttr3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr3.out_en b/python/test-data-2.0/optionalAttr3.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr3.py b/python/test-data-2.0/optionalAttr3.py new file mode 100644 index 00000000..735db5cf --- /dev/null +++ b/python/test-data-2.0/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-2.0/optionalAttr_ok.err b/python/test-data-2.0/optionalAttr_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/optionalAttr_ok.out b/python/test-data-2.0/optionalAttr_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/optionalAttr_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/optionalAttr_ok.py b/python/test-data-2.0/optionalAttr_ok.py new file mode 100644 index 00000000..eb566fc6 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/partial.err b/python/test-data-2.0/partial.err index 7c3f2643..63bc45c5 100644 --- a/python/test-data-2.0/partial.err +++ b/python/test-data-2.0/partial.err @@ -1,8 +1,8 @@ Traceback (most recent call last): - File "", line 4, in - foo(1, 2) + File "", line 1, in + def foo(i: int, j) -> None: -WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typangabe. +WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typannotation. ## Datei partial.py ## Parameter deklariert in Zeile 1: diff --git a/python/test-data-2.0/partial.err_en b/python/test-data-2.0/partial.err_en index d8609349..a94d83c8 100644 --- a/python/test-data-2.0/partial.err_en +++ b/python/test-data-2.0/partial.err_en @@ -1,6 +1,6 @@ Traceback (most recent call last): - File "", line 4, in - foo(1, 2) + File "", line 1, in + def foo(i: int, j) -> None: WyppTypeError: Parameter `j` of function `foo` requires a type annotation. diff --git a/python/test-data-2.0/tooFewArgs.err b/python/test-data-2.0/tooFewArgs.err new file mode 100644 index 00000000..2a02cacd --- /dev/null +++ b/python/test-data-2.0/tooFewArgs.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1) + +WyppTypeError: Anzahl der Argument passt nicht + +Funktion `foo` benötigt 2 Argumente. +Gegeben: 1 Argument + +## Datei tooFewArgs.py +## Aufruf in Zeile 4: + +foo(1) diff --git a/python/test-data-2.0/tooFewArgs.out b/python/test-data-2.0/tooFewArgs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooFewArgs.py b/python/test-data-2.0/tooFewArgs.py new file mode 100644 index 00000000..846d7580 --- /dev/null +++ b/python/test-data-2.0/tooFewArgs.py @@ -0,0 +1,4 @@ +def foo(i: int, j: int): + pass + +foo(1) diff --git a/python/test-data-2.0/tooFewAttrs.err b/python/test-data-2.0/tooFewAttrs.err new file mode 100644 index 00000000..56d360c5 --- /dev/null +++ b/python/test-data-2.0/tooFewAttrs.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 8, in + c = C(1) + +WyppTypeError: Anzahl der Argument passt nicht + +Konstruktor der Klasse `C` benötigt 2 Argumente. +Gegeben: 1 Argument + +## Datei tooFewAttrs.py +## Aufruf in Zeile 8: + +c = C(1) diff --git a/python/test-data-2.0/tooFewAttrs.out b/python/test-data-2.0/tooFewAttrs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooFewAttrs.py b/python/test-data-2.0/tooFewAttrs.py new file mode 100644 index 00000000..a62dcce3 --- /dev/null +++ b/python/test-data-2.0/tooFewAttrs.py @@ -0,0 +1,8 @@ +from wypp import * + +@record +class C: + x: int + y: int + +c = C(1) diff --git a/python/test-data-2.0/tooManyArgs.err b/python/test-data-2.0/tooManyArgs.err new file mode 100644 index 00000000..0419dcdb --- /dev/null +++ b/python/test-data-2.0/tooManyArgs.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1, 2, 3) + +WyppTypeError: Anzahl der Argument passt nicht + +Funktion `foo` benötigt 2 Argumente. +Gegeben: 3 Argumente + +## Datei tooManyArgs.py +## Aufruf in Zeile 4: + +foo(1, 2, 3) diff --git a/python/test-data-2.0/tooManyArgs.err_en b/python/test-data-2.0/tooManyArgs.err_en new file mode 100644 index 00000000..4582c4ea --- /dev/null +++ b/python/test-data-2.0/tooManyArgs.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 4, in + foo(1, 2, 3) + +WyppTypeError: argument count mismatch + +Function `foo` takes 2 arguments. +Given: 3 arguments + +## File tooManyArgs.py +## Call in line 4: + +foo(1, 2, 3) diff --git a/python/test-data-2.0/tooManyArgs.out b/python/test-data-2.0/tooManyArgs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooManyArgs.out_en b/python/test-data-2.0/tooManyArgs.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooManyArgs.py b/python/test-data-2.0/tooManyArgs.py new file mode 100644 index 00000000..f3c9e7a4 --- /dev/null +++ b/python/test-data-2.0/tooManyArgs.py @@ -0,0 +1,4 @@ +def foo(i: int, j: int): + pass + +foo(1, 2, 3) diff --git a/python/test-data-2.0/tooManyAttrs.err b/python/test-data-2.0/tooManyAttrs.err new file mode 100644 index 00000000..28cf37c1 --- /dev/null +++ b/python/test-data-2.0/tooManyAttrs.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", 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 tooManyAttrs.py +## Aufruf in Zeile 8: + +c = C(1, 2, 3) diff --git a/python/test-data-2.0/tooManyAttrs.err_en b/python/test-data-2.0/tooManyAttrs.err_en new file mode 100644 index 00000000..39680530 --- /dev/null +++ b/python/test-data-2.0/tooManyAttrs.err_en @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 8, in + c = C(1, 2, 3) + +WyppTypeError: argument count mismatch + +Constructor of record `C` takes 2 arguments. +Given: 3 arguments + +## File tooManyAttrs.py +## Call in line 8: + +c = C(1, 2, 3) diff --git a/python/test-data-2.0/tooManyAttrs.out b/python/test-data-2.0/tooManyAttrs.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooManyAttrs.out_en b/python/test-data-2.0/tooManyAttrs.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/tooManyAttrs.py b/python/test-data-2.0/tooManyAttrs.py new file mode 100644 index 00000000..d6230a7f --- /dev/null +++ b/python/test-data-2.0/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/tests/test_i18n.py b/python/tests/test_i18n.py index 085bf0bf..90f3eaea 100644 --- a/python/tests/test_i18n.py +++ b/python/tests/test_i18n.py @@ -2,6 +2,7 @@ import i18n import location import inspect +import utils class TestI18nTranslations(unittest.TestCase): def setUp(self): @@ -17,7 +18,7 @@ def _getCallableNameVariants(self): return [ location.CallableName('foo', 'function'), location.CallableName('foo', location.ClassMember('method', 'foo')), - location.CallableName('foo', location.ClassMember('constructor', 'foo')) + location.CallableName('foo', location.ClassMember('recordConstructor', 'foo')) ] def _getTestArgs(self, func): @@ -52,17 +53,18 @@ def _getTestArgs(self, func): return [args] if args else [[]] def testAllI18NFunctions(self): - for lang in i18n.allLanguages: - with i18n.explicitLang(lang): - with self.subTest(lang=lang): - for funcName, func in self.stringFunctions: - with self.subTest(function=funcName): - argVariants = self._getTestArgs(func) - for args in argVariants: - with self.subTest(args=args): - result = func(*args) - self.assertIsInstance(result, str) - self.assertGreater(len(result), 0) + with utils.underTest(True): + for lang in i18n.allLanguages: + with i18n.explicitLang(lang): + with self.subTest(lang=lang): + for funcName, func in self.stringFunctions: + with self.subTest(function=funcName): + argVariants = self._getTestArgs(func) + for args in argVariants: + with self.subTest(args=args): + result = func(*args) + self.assertIsInstance(result, str) + self.assertGreater(len(result), 0) if __name__ == '__main__': unittest.main() From 01392ae0b20a067863781d3df9a3ca9d1b53c3bf Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 22 Sep 2025 16:46:49 +0200 Subject: [PATCH 38/56] fixed many tests --- python/fileTests-2.0.py | 13 +++-- python/fileTestsLib.py | 56 +++++++++++++++++- python/src/__init__.py | 1 + python/src/instrument.py | 58 +++++++++++++++---- python/src/runner.py | 12 ++-- python/src/stacktrace.py | 37 ++++++++---- python/src/typecheck.py | 17 +++--- python/src/utils.py | 31 +++++++++- python/src/writeYourProgram.py | 3 +- .../{ => basics}/constructor.err | 0 .../{ => basics}/constructor.err_en | 0 .../{ => basics}/constructor.out | 0 .../{ => basics}/constructor.out_en | 0 .../test-data-2.0/{ => basics}/constructor.py | 0 .../{ => basics}/constructorDataclass_ok.py | 0 .../{ => basics}/constructor_ok.err | 0 .../{ => basics}/constructor_ok.out | 0 .../{ => basics}/constructor_ok.py | 0 .../{ => basics}/forwardRefs.err | 0 .../{ => basics}/forwardRefs.err_en | 0 .../{ => basics}/forwardRefs.out | 0 .../{ => basics}/forwardRefs.out_en | 0 .../test-data-2.0/{ => basics}/forwardRefs.py | 0 .../{ => basics}/forwardRefs_ok.err | 0 .../{ => basics}/forwardRefs_ok.out | 0 .../{ => basics}/forwardRefs_ok.py | 0 .../{ => basics}/functionArg.err | 0 .../{ => basics}/functionArg.err_en | 0 .../{ => basics}/functionArg.out | 0 .../{ => basics}/functionArg.out_en | 0 .../test-data-2.0/{ => basics}/functionArg.py | 0 .../{ => basics}/functionArg_ok.err | 0 .../{ => basics}/functionArg_ok.err_None | 0 .../{ => basics}/functionArg_ok.out | 0 .../{ => basics}/functionArg_ok.out_None | 0 .../{ => basics}/functionArg_ok.py | 0 .../{ => basics}/functionNoResult.err | 0 .../{ => basics}/functionNoResult.err_en | 0 .../{ => basics}/functionNoResult.out | 0 .../{ => basics}/functionNoResult.out_en | 0 .../{ => basics}/functionNoResult.py | 0 .../{ => basics}/functionNoResult2.err | 0 .../{ => basics}/functionNoResult2.err_en | 0 .../{ => basics}/functionNoResult2.out | 0 .../{ => basics}/functionNoResult2.out_en | 0 .../{ => basics}/functionNoResult2.py | 0 .../{ => basics}/functionNoResult2_ok.err | 0 .../{ => basics}/functionNoResult2_ok.out | 0 .../{ => basics}/functionNoResult2_ok.py | 0 .../{ => basics}/functionNoResult3.err | 0 .../{ => basics}/functionNoResult3.err_en | 0 .../{ => basics}/functionNoResult3.out | 0 .../{ => basics}/functionNoResult3.out_en | 0 .../{ => basics}/functionNoResult3.py | 0 .../{ => basics}/functionNoResult3_ok.err | 0 .../{ => basics}/functionNoResult3_ok.out | 0 .../{ => basics}/functionNoResult3_ok.py | 0 .../{ => basics}/functionNoResult_ok.err | 0 .../{ => basics}/functionNoResult_ok.out | 0 .../{ => basics}/functionNoResult_ok.py | 0 .../{ => basics}/functionResult.err | 0 .../{ => basics}/functionResult.err_en | 0 .../{ => basics}/functionResult.out | 0 .../{ => basics}/functionResult.out_en | 0 .../{ => basics}/functionResult.py | 0 .../{ => basics}/functionResult2.err | 0 .../{ => basics}/functionResult2.err_en | 0 .../{ => basics}/functionResult2.out | 0 .../{ => basics}/functionResult2.out_en | 0 .../{ => basics}/functionResult2.py | 0 .../{ => basics}/functionResult_ok.err | 0 .../{ => basics}/functionResult_ok.out | 0 .../{ => basics}/functionResult_ok.py | 0 .../test-data-2.0/{ => basics}/iterator.err | 0 .../{ => basics}/iterator.err_en | 0 .../test-data-2.0/{ => basics}/iterator.out | 0 .../{ => basics}/iterator.out_en | 0 python/test-data-2.0/{ => basics}/iterator.py | 0 .../{ => basics}/iterator_ok.err | 0 .../{ => basics}/iterator_ok.out | 0 .../test-data-2.0/{ => basics}/iterator_ok.py | 0 python/test-data-2.0/{ => basics}/listArg.err | 0 .../test-data-2.0/{ => basics}/listArg.err_en | 0 python/test-data-2.0/{ => basics}/listArg.out | 0 .../test-data-2.0/{ => basics}/listArg.out_en | 0 python/test-data-2.0/{ => basics}/listArg.py | 0 .../test-data-2.0/{ => basics}/listArg_ok.err | 0 .../test-data-2.0/{ => basics}/listArg_ok.out | 0 .../test-data-2.0/{ => basics}/listArg_ok.py | 0 .../test-data-2.0/{ => basics}/listResult.err | 0 .../{ => basics}/listResult.err_en | 0 .../test-data-2.0/{ => basics}/listResult.out | 0 .../{ => basics}/listResult.out_en | 0 .../test-data-2.0/{ => basics}/listResult.py | 0 .../{ => basics}/listResult_ok.err | 0 .../{ => basics}/listResult_ok.out | 0 .../{ => basics}/listResult_ok.py | 0 python/test-data-2.0/{ => basics}/method.err | 0 .../test-data-2.0/{ => basics}/method.err_en | 0 python/test-data-2.0/{ => basics}/method.out | 0 .../test-data-2.0/{ => basics}/method.out_en | 0 python/test-data-2.0/{ => basics}/method.py | 0 .../test-data-2.0/{ => basics}/method_ok.err | 0 .../test-data-2.0/{ => basics}/method_ok.out | 0 .../test-data-2.0/{ => basics}/method_ok.py | 0 python/test-data-2.0/{ => basics}/mutable.err | 0 .../test-data-2.0/{ => basics}/mutable.err_en | 0 python/test-data-2.0/{ => basics}/mutable.out | 0 .../test-data-2.0/{ => basics}/mutable.out_en | 0 python/test-data-2.0/{ => basics}/mutable.py | 0 .../test-data-2.0/{ => basics}/mutable2.err | 2 +- .../{ => basics}/mutable2.err_en | 2 +- .../test-data-2.0/{ => basics}/mutable2.out | 0 .../{ => basics}/mutable2.out_en | 0 python/test-data-2.0/{ => basics}/mutable2.py | 0 .../{ => basics}/mutable2_ok.err | 0 .../{ => basics}/mutable2_ok.out | 0 .../test-data-2.0/{ => basics}/mutable2_ok.py | 0 .../test-data-2.0/{ => basics}/mutable_ok.err | 0 .../test-data-2.0/{ => basics}/mutable_ok.out | 0 .../test-data-2.0/{ => basics}/mutable_ok.py | 0 python/test-data-2.0/{ => basics}/nested.err | 0 python/test-data-2.0/{ => basics}/nested.out | 0 python/test-data-2.0/{ => basics}/nested.py | 0 .../test-data-2.0/{ => basics}/nosig_ok.err | 0 .../test-data-2.0/{ => basics}/nosig_ok.out | 0 python/test-data-2.0/{ => basics}/nosig_ok.py | 0 .../{ => basics}/optionalArgs.err | 0 .../{ => basics}/optionalArgs.err_en | 0 .../{ => basics}/optionalArgs.out | 0 .../{ => basics}/optionalArgs.out_en | 0 .../{ => basics}/optionalArgs.py | 0 .../{ => basics}/optionalArgs2.err | 0 .../{ => basics}/optionalArgs2.err_en | 0 .../{ => basics}/optionalArgs2.out | 0 .../{ => basics}/optionalArgs2.out_en | 0 .../{ => basics}/optionalArgs2.py | 0 .../{ => basics}/optionalArgs3.err | 0 .../{ => basics}/optionalArgs3.err_en | 0 .../{ => basics}/optionalArgs3.out | 0 .../{ => basics}/optionalArgs3.out_en | 0 .../{ => basics}/optionalArgs3.py | 0 .../{ => basics}/optionalArgs4.err | 0 .../{ => basics}/optionalArgs4.err_en | 0 .../{ => basics}/optionalArgs4.out | 0 .../{ => basics}/optionalArgs4.out_en | 0 .../{ => basics}/optionalArgs4.py | 0 .../{ => basics}/optionalArgs_ok.err | 0 .../{ => basics}/optionalArgs_ok.out | 0 .../{ => basics}/optionalArgs_ok.py | 0 .../{ => basics}/optionalAttr.err | 0 .../{ => basics}/optionalAttr.err_en | 0 .../{ => basics}/optionalAttr.out | 0 .../{ => basics}/optionalAttr.out_en | 0 .../{ => basics}/optionalAttr.py | 0 .../{ => basics}/optionalAttr2.err | 2 +- .../{ => basics}/optionalAttr2.err_en | 2 +- .../{ => basics}/optionalAttr2.out | 0 .../{ => basics}/optionalAttr2.out_en | 0 .../{ => basics}/optionalAttr2.py | 0 .../{ => basics}/optionalAttr3.err | 2 +- .../{ => basics}/optionalAttr3.err_en | 2 +- .../{ => basics}/optionalAttr3.out | 0 .../{ => basics}/optionalAttr3.out_en | 0 .../{ => basics}/optionalAttr3.py | 0 .../{ => basics}/optionalAttr_ok.err | 0 .../{ => basics}/optionalAttr_ok.out | 0 .../{ => basics}/optionalAttr_ok.py | 0 python/test-data-2.0/{ => basics}/partial.err | 0 .../test-data-2.0/{ => basics}/partial.err_en | 0 python/test-data-2.0/{ => basics}/partial.out | 0 .../test-data-2.0/{ => basics}/partial.out_en | 0 python/test-data-2.0/{ => basics}/partial.py | 0 python/test-data-2.0/{ => basics}/record.err | 2 +- .../test-data-2.0/{ => basics}/record.err_en | 2 +- python/test-data-2.0/{ => basics}/record.out | 0 .../test-data-2.0/{ => basics}/record.out_en | 0 python/test-data-2.0/{ => basics}/record.py | 0 .../test-data-2.0/{ => basics}/record_ok.err | 0 .../test-data-2.0/{ => basics}/record_ok.out | 0 .../test-data-2.0/{ => basics}/record_ok.py | 0 python/test-data-2.0/basics/stack.err | 13 +++++ .../{staticmethod.out => basics/stack.out} | 0 python/test-data-2.0/basics/stack.py | 7 +++ .../{ => basics}/staticmethod.err | 0 .../{ => basics}/staticmethod.err_en | 0 .../staticmethod.out} | 0 .../staticmethod.out_en} | 0 .../{ => basics}/staticmethod.py | 0 .../staticmethod_ok.err} | 0 .../{ => basics}/staticmethod_ok.out | 0 .../{ => basics}/staticmethod_ok.py | 0 .../{ => basics}/testCallable.err | 0 .../testCallable.out} | 0 .../{ => basics}/testCallable.py | 0 .../test-data-2.0/{ => basics}/tooFewArgs.err | 0 .../tooFewArgs.out} | 0 .../test-data-2.0/{ => basics}/tooFewArgs.py | 0 .../{ => basics}/tooFewAttrs.err | 2 +- .../tooFewAttrs.out} | 0 .../test-data-2.0/{ => basics}/tooFewAttrs.py | 0 .../{ => basics}/tooManyArgs.err | 0 .../{ => basics}/tooManyArgs.err_en | 0 .../tooManyArgs.out} | 0 .../tooManyArgs.out_en} | 0 .../test-data-2.0/{ => basics}/tooManyArgs.py | 0 .../{ => basics}/tooManyAttrs.err | 0 .../{ => basics}/tooManyAttrs.err_en | 0 .../tooManyAttrs.out} | 0 .../basics/tooManyAttrs.out_en} | 0 .../{ => basics}/tooManyAttrs.py | 0 .../extras/admin_ok.err} | 0 .../extras/admin_ok.out} | 0 .../extras/admin_ok.py} | 0 .../extras/printModuleNameImport_ok.err} | 0 .../extras/printModuleNameImport_ok.out} | 2 +- .../extras/printModuleNameImport_ok.py | 12 ++++ .../extras/printModuleName_ok.err} | 0 .../extras/printModuleName_ok.out} | 0 .../extras/printModuleName_ok.py} | 1 + .../extras/testABC_ok.err} | 0 .../extras/testABC_ok.out} | 0 .../extras/testABC_ok.py} | 0 .../extras/testBugSliceIndices_ok.err} | 0 .../extras/testBugSliceIndices_ok.out} | 0 .../extras/testBugSliceIndices_ok.py} | 0 .../extras/testCheckFail_ok.err} | 0 .../extras/testCheckFail_ok.out} | 0 .../extras/testCheckFail_ok.py} | 1 + .../extras/testClassHierarchy_ok.err} | 0 .../extras/testClassHierarchy_ok.out} | 0 .../extras/testClassHierarchy_ok.py} | 0 .../extras/testClassRecursion_ok.err} | 0 .../extras/testClassRecursion_ok.out} | 0 .../extras/testClassRecursion_ok.py} | 0 .../extras/testComplex_ok.err} | 0 .../extras/testComplex_ok.out} | 0 .../extras/testComplex_ok.py} | 0 .../extras/testConcat_ok.err} | 0 .../extras/testConcat_ok.out} | 0 .../extras/testConcat_ok.py} | 0 .../extras/testCopy_ok.err} | 0 .../extras/testCopy_ok.out} | 0 .../extras/testCopy_ok.py} | 0 .../extras/testDict_ok.err} | 0 .../extras/testDict_ok.out} | 0 .../extras/testDict_ok.py} | 0 .../extras/testDoubleWrappingDicts_ok.err} | 0 .../extras/testDoubleWrappingDicts_ok.out} | 0 .../extras/testDoubleWrappingDicts_ok.py} | 0 .../extras/testForwardRef1_ok.err} | 0 .../extras/testForwardRef1_ok.out} | 0 .../extras/testForwardRef1_ok.py} | 0 .../extras/testForwardRef3_ok.err} | 0 .../extras/testForwardRef3_ok.out} | 0 .../extras/testForwardRef3_ok.py} | 0 .../extras/testForwardRef6_ok.err} | 0 .../extras/testForwardRef6_ok.out} | 0 .../extras/testForwardRef6_ok.py} | 0 .../extras/testForwardRef_ok.err} | 0 .../extras/testForwardRef_ok.out} | 0 .../extras/testForwardRef_ok.py} | 0 .../extras/testForwardTypeInRecord2_ok.err} | 0 .../extras/testForwardTypeInRecord2_ok.out} | 0 .../extras/testForwardTypeInRecord2_ok.py} | 0 .../extras/testForwardTypeInRecord_ok.err} | 0 .../extras/testForwardTypeInRecord_ok.out} | 0 .../extras/testForwardTypeInRecord_ok.py} | 0 .../extras/testHof_ok.err} | 0 .../extras/testHof_ok.out} | 0 .../extras/testHof_ok.py} | 0 .../extras/testIndexSeq_ok.err} | 0 .../extras/testIndexSeq_ok.out} | 0 .../extras/testIndexSeq_ok.py} | 0 .../extras/testIterable1_ok.err} | 0 .../extras/testIterable1_ok.out} | 0 .../extras/testIterable1_ok.py} | 0 .../extras/testIterable2_ok.err} | 0 .../extras/testIterable2_ok.out} | 0 .../extras/testIterable2_ok.py} | 0 .../extras/testIterable3_ok.err} | 0 .../extras/testIterable3_ok.out} | 0 .../extras/testIterable3_ok.py} | 0 .../extras/testIterable4_ok.err} | 0 .../extras/testIterable4_ok.out} | 0 .../extras/testIterable4_ok.py} | 0 .../extras/testIterable5_ok.err} | 0 .../extras/testIterable5_ok.out} | 0 .../extras/testIterable5_ok.py} | 0 .../extras/testIterable6_ok.err} | 0 .../extras/testIterable6_ok.out} | 0 .../extras/testIterable6_ok.py} | 0 .../extras/testIterator2_ok.err} | 0 .../extras/testIterator2_ok.out} | 0 .../extras/testIterator2_ok.py} | 0 .../extras/testIterator3_ok.err} | 0 .../extras/testIterator3_ok.out} | 0 .../extras/testIterator3_ok.py} | 0 .../extras/testIterator4_ok.err} | 0 .../extras/testIterator4_ok.out} | 0 .../extras/testIterator4_ok.py} | 0 .../extras/testIterator_ok.err} | 0 .../extras/testIterator_ok.out} | 0 .../extras/testIterator_ok.py} | 0 .../extras/testLiteralInstanceOf_ok.err} | 0 .../extras/testLiteralInstanceOf_ok.out} | 0 .../extras/testLiteralInstanceOf_ok.py} | 0 .../extras/testNameErrorBug_ok.err} | 0 .../extras/testNameErrorBug_ok.out} | 0 .../extras/testNameErrorBug_ok.py} | 0 .../extras/testTypeKeyword_ok.err} | 0 .../extras/testTypeKeyword_ok.out} | 0 .../extras/testTypeKeyword_ok.py} | 1 + python/test-data-2.0/extras/testTypes2.err | 17 ++++++ .../extras/testTypes2.err-notypes} | 0 .../extras/testTypes2.out} | 0 .../extras}/testTypes2.py | 0 python/test-data-2.0/extras/testTypes2_ok.py | 5 ++ .../extras/testTypesHigherOrderFuns2_ok.err} | 0 .../extras/testTypesHigherOrderFuns2_ok.out} | 0 .../extras/testTypesHigherOrderFuns2_ok.py} | 0 .../extras/testTypesHigherOrderFuns4_ok.err} | 0 .../extras/testTypesHigherOrderFuns4_ok.out} | 0 .../extras/testTypesHigherOrderFuns4_ok.py} | 0 .../extras/testTypesHigherOrderFuns5_ok.err} | 0 .../extras/testTypesHigherOrderFuns5_ok.out} | 0 .../extras/testTypesHigherOrderFuns5_ok.py} | 0 .../extras/testTypesProtos5_ok.err} | 0 .../extras/testTypesProtos5_ok.out} | 0 .../extras/testTypesProtos5_ok.py} | 0 .../extras/testTypesWrapperEq_ok.err} | 0 .../extras/testTypesWrapperEq_ok.out} | 0 .../extras/testTypesWrapperEq_ok.py} | 0 .../extras/testUnion2_ok.err} | 0 .../extras/testUnion2_ok.out} | 0 .../extras/testUnion2_ok.py} | 0 .../extras/testUnion3_ok.err} | 0 .../extras/testUnion3_ok.out} | 0 .../extras/testUnion3_ok.py} | 0 .../extras/testUnionLiteral_ok.err} | 0 .../extras/testUnionLiteral_ok.out} | 0 .../extras/testUnionLiteral_ok.py} | 0 .../extras/testUnionOfUnion_ok.err} | 0 .../extras/testUnionOfUnion_ok.out} | 0 .../extras/testUnionOfUnion_ok.py} | 0 .../extras/testUnion_ok.err} | 0 .../extras/testUnion_ok.out} | 0 .../extras/testUnion_ok.py} | 0 python/test-data/modules/A/main.py | 2 +- python/test-data/printModuleNameImport.py | 11 ---- python/test-data/testHintParentheses2.py | 4 +- python/test-data/testHintParentheses3.py | 2 + python/test-data/testHintParentheses4.py | 9 +++ python/test-data/testHintParentheses5.py | 9 +++ python/test-data/testHintParentheses6.py | 10 ++++ python/test-data/testInvalidLiteral.py | 2 + python/test-data/testTypes2.err | 12 ---- python/test-data/testTypesCollections3.err | 14 ----- python/test-data/testTypesCollections3.py | 8 --- python/test-data/testTypesCollections4.err | 14 ----- python/test-data/testTypesCollections4.py | 11 ---- ...testTypesDict1.py => testTypesDict1_ok.py} | 0 python/test-data/testTypesHigherOrderFuns3.py | 2 +- .../{testTypesSet1.py => testTypesSet1_ok.py} | 0 python/test-data/testUnionOfUnion.err | 0 python/test-data/testWrap.err | 0 python/test-data/testWrap.out | 1 - python/test-data/testWrap.py | 28 --------- python/test-data/testWrap2.err | 0 python/test-data/testWrap2.out | 1 - python/test-data/testWrap2.py | 28 --------- python/test-data/testWrapperError.err | 8 --- python/test-data/testWrapperError.out | 0 python/test-data/testWrapperError.py | 9 --- 374 files changed, 282 insertions(+), 208 deletions(-) rename python/test-data-2.0/{ => basics}/constructor.err (100%) rename python/test-data-2.0/{ => basics}/constructor.err_en (100%) rename python/test-data-2.0/{ => basics}/constructor.out (100%) rename python/test-data-2.0/{ => basics}/constructor.out_en (100%) rename python/test-data-2.0/{ => basics}/constructor.py (100%) rename python/test-data-2.0/{ => basics}/constructorDataclass_ok.py (100%) rename python/test-data-2.0/{ => basics}/constructor_ok.err (100%) rename python/test-data-2.0/{ => basics}/constructor_ok.out (100%) rename python/test-data-2.0/{ => basics}/constructor_ok.py (100%) rename python/test-data-2.0/{ => basics}/forwardRefs.err (100%) rename python/test-data-2.0/{ => basics}/forwardRefs.err_en (100%) rename python/test-data-2.0/{ => basics}/forwardRefs.out (100%) rename python/test-data-2.0/{ => basics}/forwardRefs.out_en (100%) rename python/test-data-2.0/{ => basics}/forwardRefs.py (100%) rename python/test-data-2.0/{ => basics}/forwardRefs_ok.err (100%) rename python/test-data-2.0/{ => basics}/forwardRefs_ok.out (100%) rename python/test-data-2.0/{ => basics}/forwardRefs_ok.py (100%) rename python/test-data-2.0/{ => basics}/functionArg.err (100%) rename python/test-data-2.0/{ => basics}/functionArg.err_en (100%) rename python/test-data-2.0/{ => basics}/functionArg.out (100%) rename python/test-data-2.0/{ => basics}/functionArg.out_en (100%) rename python/test-data-2.0/{ => basics}/functionArg.py (100%) rename python/test-data-2.0/{ => basics}/functionArg_ok.err (100%) rename python/test-data-2.0/{ => basics}/functionArg_ok.err_None (100%) rename python/test-data-2.0/{ => basics}/functionArg_ok.out (100%) rename python/test-data-2.0/{ => basics}/functionArg_ok.out_None (100%) rename python/test-data-2.0/{ => basics}/functionArg_ok.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult.err_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult.out_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2.err_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2.out_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2_ok.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2_ok.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult2_ok.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3.err_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3.out_en (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3_ok.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3_ok.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult3_ok.py (100%) rename python/test-data-2.0/{ => basics}/functionNoResult_ok.err (100%) rename python/test-data-2.0/{ => basics}/functionNoResult_ok.out (100%) rename python/test-data-2.0/{ => basics}/functionNoResult_ok.py (100%) rename python/test-data-2.0/{ => basics}/functionResult.err (100%) rename python/test-data-2.0/{ => basics}/functionResult.err_en (100%) rename python/test-data-2.0/{ => basics}/functionResult.out (100%) rename python/test-data-2.0/{ => basics}/functionResult.out_en (100%) rename python/test-data-2.0/{ => basics}/functionResult.py (100%) rename python/test-data-2.0/{ => basics}/functionResult2.err (100%) rename python/test-data-2.0/{ => basics}/functionResult2.err_en (100%) rename python/test-data-2.0/{ => basics}/functionResult2.out (100%) rename python/test-data-2.0/{ => basics}/functionResult2.out_en (100%) rename python/test-data-2.0/{ => basics}/functionResult2.py (100%) rename python/test-data-2.0/{ => basics}/functionResult_ok.err (100%) rename python/test-data-2.0/{ => basics}/functionResult_ok.out (100%) rename python/test-data-2.0/{ => basics}/functionResult_ok.py (100%) rename python/test-data-2.0/{ => basics}/iterator.err (100%) rename python/test-data-2.0/{ => basics}/iterator.err_en (100%) rename python/test-data-2.0/{ => basics}/iterator.out (100%) rename python/test-data-2.0/{ => basics}/iterator.out_en (100%) rename python/test-data-2.0/{ => basics}/iterator.py (100%) rename python/test-data-2.0/{ => basics}/iterator_ok.err (100%) rename python/test-data-2.0/{ => basics}/iterator_ok.out (100%) rename python/test-data-2.0/{ => basics}/iterator_ok.py (100%) rename python/test-data-2.0/{ => basics}/listArg.err (100%) rename python/test-data-2.0/{ => basics}/listArg.err_en (100%) rename python/test-data-2.0/{ => basics}/listArg.out (100%) rename python/test-data-2.0/{ => basics}/listArg.out_en (100%) rename python/test-data-2.0/{ => basics}/listArg.py (100%) rename python/test-data-2.0/{ => basics}/listArg_ok.err (100%) rename python/test-data-2.0/{ => basics}/listArg_ok.out (100%) rename python/test-data-2.0/{ => basics}/listArg_ok.py (100%) rename python/test-data-2.0/{ => basics}/listResult.err (100%) rename python/test-data-2.0/{ => basics}/listResult.err_en (100%) rename python/test-data-2.0/{ => basics}/listResult.out (100%) rename python/test-data-2.0/{ => basics}/listResult.out_en (100%) rename python/test-data-2.0/{ => basics}/listResult.py (100%) rename python/test-data-2.0/{ => basics}/listResult_ok.err (100%) rename python/test-data-2.0/{ => basics}/listResult_ok.out (100%) rename python/test-data-2.0/{ => basics}/listResult_ok.py (100%) rename python/test-data-2.0/{ => basics}/method.err (100%) rename python/test-data-2.0/{ => basics}/method.err_en (100%) rename python/test-data-2.0/{ => basics}/method.out (100%) rename python/test-data-2.0/{ => basics}/method.out_en (100%) rename python/test-data-2.0/{ => basics}/method.py (100%) rename python/test-data-2.0/{ => basics}/method_ok.err (100%) rename python/test-data-2.0/{ => basics}/method_ok.out (100%) rename python/test-data-2.0/{ => basics}/method_ok.py (100%) rename python/test-data-2.0/{ => basics}/mutable.err (100%) rename python/test-data-2.0/{ => basics}/mutable.err_en (100%) rename python/test-data-2.0/{ => basics}/mutable.out (100%) rename python/test-data-2.0/{ => basics}/mutable.out_en (100%) rename python/test-data-2.0/{ => basics}/mutable.py (100%) rename python/test-data-2.0/{ => basics}/mutable2.err (75%) rename python/test-data-2.0/{ => basics}/mutable2.err_en (76%) rename python/test-data-2.0/{ => basics}/mutable2.out (100%) rename python/test-data-2.0/{ => basics}/mutable2.out_en (100%) rename python/test-data-2.0/{ => basics}/mutable2.py (100%) rename python/test-data-2.0/{ => basics}/mutable2_ok.err (100%) rename python/test-data-2.0/{ => basics}/mutable2_ok.out (100%) rename python/test-data-2.0/{ => basics}/mutable2_ok.py (100%) rename python/test-data-2.0/{ => basics}/mutable_ok.err (100%) rename python/test-data-2.0/{ => basics}/mutable_ok.out (100%) rename python/test-data-2.0/{ => basics}/mutable_ok.py (100%) rename python/test-data-2.0/{ => basics}/nested.err (100%) rename python/test-data-2.0/{ => basics}/nested.out (100%) rename python/test-data-2.0/{ => basics}/nested.py (100%) rename python/test-data-2.0/{ => basics}/nosig_ok.err (100%) rename python/test-data-2.0/{ => basics}/nosig_ok.out (100%) rename python/test-data-2.0/{ => basics}/nosig_ok.py (100%) rename python/test-data-2.0/{ => basics}/optionalArgs.err (100%) rename python/test-data-2.0/{ => basics}/optionalArgs.err_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs.out (100%) rename python/test-data-2.0/{ => basics}/optionalArgs.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs.py (100%) rename python/test-data-2.0/{ => basics}/optionalArgs2.err (100%) rename python/test-data-2.0/{ => basics}/optionalArgs2.err_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs2.out (100%) rename python/test-data-2.0/{ => basics}/optionalArgs2.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs2.py (100%) rename python/test-data-2.0/{ => basics}/optionalArgs3.err (100%) rename python/test-data-2.0/{ => basics}/optionalArgs3.err_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs3.out (100%) rename python/test-data-2.0/{ => basics}/optionalArgs3.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs3.py (100%) rename python/test-data-2.0/{ => basics}/optionalArgs4.err (100%) rename python/test-data-2.0/{ => basics}/optionalArgs4.err_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs4.out (100%) rename python/test-data-2.0/{ => basics}/optionalArgs4.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalArgs4.py (100%) rename python/test-data-2.0/{ => basics}/optionalArgs_ok.err (100%) rename python/test-data-2.0/{ => basics}/optionalArgs_ok.out (100%) rename python/test-data-2.0/{ => basics}/optionalArgs_ok.py (100%) rename python/test-data-2.0/{ => basics}/optionalAttr.err (100%) rename python/test-data-2.0/{ => basics}/optionalAttr.err_en (100%) rename python/test-data-2.0/{ => basics}/optionalAttr.out (100%) rename python/test-data-2.0/{ => basics}/optionalAttr.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalAttr.py (100%) rename python/test-data-2.0/{ => basics}/optionalAttr2.err (78%) rename python/test-data-2.0/{ => basics}/optionalAttr2.err_en (79%) rename python/test-data-2.0/{ => basics}/optionalAttr2.out (100%) rename python/test-data-2.0/{ => basics}/optionalAttr2.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalAttr2.py (100%) rename python/test-data-2.0/{ => basics}/optionalAttr3.err (79%) rename python/test-data-2.0/{ => basics}/optionalAttr3.err_en (81%) rename python/test-data-2.0/{ => basics}/optionalAttr3.out (100%) rename python/test-data-2.0/{ => basics}/optionalAttr3.out_en (100%) rename python/test-data-2.0/{ => basics}/optionalAttr3.py (100%) rename python/test-data-2.0/{ => basics}/optionalAttr_ok.err (100%) rename python/test-data-2.0/{ => basics}/optionalAttr_ok.out (100%) rename python/test-data-2.0/{ => basics}/optionalAttr_ok.py (100%) rename python/test-data-2.0/{ => basics}/partial.err (100%) rename python/test-data-2.0/{ => basics}/partial.err_en (100%) rename python/test-data-2.0/{ => basics}/partial.out (100%) rename python/test-data-2.0/{ => basics}/partial.out_en (100%) rename python/test-data-2.0/{ => basics}/partial.py (100%) rename python/test-data-2.0/{ => basics}/record.err (76%) rename python/test-data-2.0/{ => basics}/record.err_en (77%) rename python/test-data-2.0/{ => basics}/record.out (100%) rename python/test-data-2.0/{ => basics}/record.out_en (100%) rename python/test-data-2.0/{ => basics}/record.py (100%) rename python/test-data-2.0/{ => basics}/record_ok.err (100%) rename python/test-data-2.0/{ => basics}/record_ok.out (100%) rename python/test-data-2.0/{ => basics}/record_ok.py (100%) create mode 100644 python/test-data-2.0/basics/stack.err rename python/test-data-2.0/{staticmethod.out => basics/stack.out} (100%) create mode 100644 python/test-data-2.0/basics/stack.py rename python/test-data-2.0/{ => basics}/staticmethod.err (100%) rename python/test-data-2.0/{ => basics}/staticmethod.err_en (100%) rename python/test-data-2.0/{staticmethod.out_en => basics/staticmethod.out} (100%) rename python/test-data-2.0/{staticmethod_ok.err => basics/staticmethod.out_en} (100%) rename python/test-data-2.0/{ => basics}/staticmethod.py (100%) rename python/test-data-2.0/{testCallable.out => basics/staticmethod_ok.err} (100%) rename python/test-data-2.0/{ => basics}/staticmethod_ok.out (100%) rename python/test-data-2.0/{ => basics}/staticmethod_ok.py (100%) rename python/test-data-2.0/{ => basics}/testCallable.err (100%) rename python/test-data-2.0/{tooFewArgs.out => basics/testCallable.out} (100%) rename python/test-data-2.0/{ => basics}/testCallable.py (100%) rename python/test-data-2.0/{ => basics}/tooFewArgs.err (100%) rename python/test-data-2.0/{tooFewAttrs.out => basics/tooFewArgs.out} (100%) rename python/test-data-2.0/{ => basics}/tooFewArgs.py (100%) rename python/test-data-2.0/{ => basics}/tooFewAttrs.err (81%) rename python/test-data-2.0/{tooManyArgs.out => basics/tooFewAttrs.out} (100%) rename python/test-data-2.0/{ => basics}/tooFewAttrs.py (100%) rename python/test-data-2.0/{ => basics}/tooManyArgs.err (100%) rename python/test-data-2.0/{ => basics}/tooManyArgs.err_en (100%) rename python/test-data-2.0/{tooManyArgs.out_en => basics/tooManyArgs.out} (100%) rename python/test-data-2.0/{tooManyAttrs.out => basics/tooManyArgs.out_en} (100%) rename python/test-data-2.0/{ => basics}/tooManyArgs.py (100%) rename python/test-data-2.0/{ => basics}/tooManyAttrs.err (100%) rename python/test-data-2.0/{ => basics}/tooManyAttrs.err_en (100%) rename python/test-data-2.0/{tooManyAttrs.out_en => basics/tooManyAttrs.out} (100%) rename python/{test-data/admin.err => test-data-2.0/basics/tooManyAttrs.out_en} (100%) rename python/test-data-2.0/{ => basics}/tooManyAttrs.py (100%) rename python/{test-data/printModuleName.err => test-data-2.0/extras/admin_ok.err} (100%) rename python/{test-data/admin.out => test-data-2.0/extras/admin_ok.out} (100%) rename python/{test-data/admin.py => test-data-2.0/extras/admin_ok.py} (100%) rename python/{test-data/printModuleNameImport.err => test-data-2.0/extras/printModuleNameImport_ok.err} (100%) rename python/{test-data/printModuleNameImport.out => test-data-2.0/extras/printModuleNameImport_ok.out} (55%) create mode 100644 python/test-data-2.0/extras/printModuleNameImport_ok.py rename python/{test-data/testABC.err => test-data-2.0/extras/printModuleName_ok.err} (100%) rename python/{test-data/printModuleName.out => test-data-2.0/extras/printModuleName_ok.out} (100%) rename python/{test-data/printModuleName.py => test-data-2.0/extras/printModuleName_ok.py} (83%) rename python/{test-data/testBugSliceIndices.err => test-data-2.0/extras/testABC_ok.err} (100%) rename python/{test-data/testABC.out => test-data-2.0/extras/testABC_ok.out} (100%) rename python/{test-data/testABC.py => test-data-2.0/extras/testABC_ok.py} (100%) rename python/{test-data/testCheckFail.err => test-data-2.0/extras/testBugSliceIndices_ok.err} (100%) rename python/{test-data/testBugSliceIndices.out => test-data-2.0/extras/testBugSliceIndices_ok.out} (100%) rename python/{test-data/testBugSliceIndices.py => test-data-2.0/extras/testBugSliceIndices_ok.py} (100%) rename python/{test-data/testClassHierarchy.err => test-data-2.0/extras/testCheckFail_ok.err} (100%) rename python/{test-data/testCheckFail.out => test-data-2.0/extras/testCheckFail_ok.out} (100%) rename python/{test-data/testCheckFail.py => test-data-2.0/extras/testCheckFail_ok.py} (57%) rename python/{test-data/testClassRecursion.err => test-data-2.0/extras/testClassHierarchy_ok.err} (100%) rename python/{test-data/testClassHierarchy.out => test-data-2.0/extras/testClassHierarchy_ok.out} (100%) rename python/{test-data/testClassHierarchy.py => test-data-2.0/extras/testClassHierarchy_ok.py} (100%) rename python/{test-data/testComplex.err => test-data-2.0/extras/testClassRecursion_ok.err} (100%) rename python/{test-data/testClassRecursion.out => test-data-2.0/extras/testClassRecursion_ok.out} (100%) rename python/{test-data/testClassRecursion.py => test-data-2.0/extras/testClassRecursion_ok.py} (100%) rename python/{test-data/testComplex.out => test-data-2.0/extras/testComplex_ok.err} (100%) rename python/{test-data/testConcat.err => test-data-2.0/extras/testComplex_ok.out} (100%) rename python/{test-data/testComplex.py => test-data-2.0/extras/testComplex_ok.py} (100%) rename python/{test-data/testCopy.err => test-data-2.0/extras/testConcat_ok.err} (100%) rename python/{test-data/testConcat.out => test-data-2.0/extras/testConcat_ok.out} (100%) rename python/{test-data/testConcat.py => test-data-2.0/extras/testConcat_ok.py} (100%) rename python/{test-data/testDict.err => test-data-2.0/extras/testCopy_ok.err} (100%) rename python/{test-data/testCopy.out => test-data-2.0/extras/testCopy_ok.out} (100%) rename python/{test-data/testCopy.py => test-data-2.0/extras/testCopy_ok.py} (100%) rename python/{test-data/testDoubleWrappingDicts.err => test-data-2.0/extras/testDict_ok.err} (100%) rename python/{test-data/testDict.out => test-data-2.0/extras/testDict_ok.out} (100%) rename python/{test-data/testDict.py => test-data-2.0/extras/testDict_ok.py} (100%) rename python/{test-data/testForwardRef.err => test-data-2.0/extras/testDoubleWrappingDicts_ok.err} (100%) rename python/{test-data/testDoubleWrappingDicts.out => test-data-2.0/extras/testDoubleWrappingDicts_ok.out} (100%) rename python/{test-data/testDoubleWrappingDicts.py => test-data-2.0/extras/testDoubleWrappingDicts_ok.py} (100%) rename python/{test-data/testForwardRef.out => test-data-2.0/extras/testForwardRef1_ok.err} (100%) rename python/{test-data/testForwardRef1.out => test-data-2.0/extras/testForwardRef1_ok.out} (100%) rename python/{test-data/testForwardRef1.py => test-data-2.0/extras/testForwardRef1_ok.py} (100%) rename python/{test-data/testForwardRef1.err => test-data-2.0/extras/testForwardRef3_ok.err} (100%) rename python/{test-data/testForwardRef3.out => test-data-2.0/extras/testForwardRef3_ok.out} (100%) rename python/{test-data/testForwardRef3.py => test-data-2.0/extras/testForwardRef3_ok.py} (100%) rename python/{test-data/testForwardRef3.err => test-data-2.0/extras/testForwardRef6_ok.err} (100%) rename python/{test-data/testForwardRef6.out => test-data-2.0/extras/testForwardRef6_ok.out} (100%) rename python/{test-data/testForwardRef6.py => test-data-2.0/extras/testForwardRef6_ok.py} (100%) rename python/{test-data/testForwardRef6.err => test-data-2.0/extras/testForwardRef_ok.err} (100%) rename python/{test-data/testForwardTypeInRecord.err => test-data-2.0/extras/testForwardRef_ok.out} (100%) rename python/{test-data/testForwardRef.py => test-data-2.0/extras/testForwardRef_ok.py} (100%) rename python/{test-data/testForwardTypeInRecord2.err => test-data-2.0/extras/testForwardTypeInRecord2_ok.err} (100%) rename python/{test-data/testForwardTypeInRecord.out => test-data-2.0/extras/testForwardTypeInRecord2_ok.out} (100%) rename python/{test-data/testForwardTypeInRecord2.py => test-data-2.0/extras/testForwardTypeInRecord2_ok.py} (100%) rename python/{test-data/testHof.err => test-data-2.0/extras/testForwardTypeInRecord_ok.err} (100%) rename python/{test-data/testForwardTypeInRecord2.out => test-data-2.0/extras/testForwardTypeInRecord_ok.out} (100%) rename python/{test-data/testForwardTypeInRecord.py => test-data-2.0/extras/testForwardTypeInRecord_ok.py} (100%) rename python/{test-data/testIndexSeq.err => test-data-2.0/extras/testHof_ok.err} (100%) rename python/{test-data/testHof.out => test-data-2.0/extras/testHof_ok.out} (100%) rename python/{test-data/testHof.py => test-data-2.0/extras/testHof_ok.py} (100%) rename python/{test-data/testIterable1.err => test-data-2.0/extras/testIndexSeq_ok.err} (100%) rename python/{test-data/testIndexSeq.out => test-data-2.0/extras/testIndexSeq_ok.out} (100%) rename python/{test-data/testIndexSeq.py => test-data-2.0/extras/testIndexSeq_ok.py} (100%) rename python/{test-data/testIterable2.err => test-data-2.0/extras/testIterable1_ok.err} (100%) rename python/{test-data/testIterable1.out => test-data-2.0/extras/testIterable1_ok.out} (100%) rename python/{test-data/testIterable1.py => test-data-2.0/extras/testIterable1_ok.py} (100%) rename python/{test-data/testIterable3.err => test-data-2.0/extras/testIterable2_ok.err} (100%) rename python/{test-data/testIterable2.out => test-data-2.0/extras/testIterable2_ok.out} (100%) rename python/{test-data/testIterable2.py => test-data-2.0/extras/testIterable2_ok.py} (100%) rename python/{test-data/testIterable4.err => test-data-2.0/extras/testIterable3_ok.err} (100%) rename python/{test-data/testIterable3.out => test-data-2.0/extras/testIterable3_ok.out} (100%) rename python/{test-data/testIterable3.py => test-data-2.0/extras/testIterable3_ok.py} (100%) rename python/{test-data/testIterable5.err => test-data-2.0/extras/testIterable4_ok.err} (100%) rename python/{test-data/testIterable4.out => test-data-2.0/extras/testIterable4_ok.out} (100%) rename python/{test-data/testIterable4.py => test-data-2.0/extras/testIterable4_ok.py} (100%) rename python/{test-data/testIterable6.err => test-data-2.0/extras/testIterable5_ok.err} (100%) rename python/{test-data/testIterable5.out => test-data-2.0/extras/testIterable5_ok.out} (100%) rename python/{test-data/testIterable5.py => test-data-2.0/extras/testIterable5_ok.py} (100%) rename python/{test-data/testIterator.err => test-data-2.0/extras/testIterable6_ok.err} (100%) rename python/{test-data/testIterable6.out => test-data-2.0/extras/testIterable6_ok.out} (100%) rename python/{test-data/testIterable6.py => test-data-2.0/extras/testIterable6_ok.py} (100%) rename python/{test-data/testIterator2.err => test-data-2.0/extras/testIterator2_ok.err} (100%) rename python/{test-data/testIterator.out => test-data-2.0/extras/testIterator2_ok.out} (100%) rename python/{test-data/testIterator2.py => test-data-2.0/extras/testIterator2_ok.py} (100%) rename python/{test-data/testIterator3.err => test-data-2.0/extras/testIterator3_ok.err} (100%) rename python/{test-data/testIterator3.out => test-data-2.0/extras/testIterator3_ok.out} (100%) rename python/{test-data/testIterator3.py => test-data-2.0/extras/testIterator3_ok.py} (100%) rename python/{test-data/testIterator4.err => test-data-2.0/extras/testIterator4_ok.err} (100%) rename python/{test-data/testIterator4.out => test-data-2.0/extras/testIterator4_ok.out} (100%) rename python/{test-data/testIterator4.py => test-data-2.0/extras/testIterator4_ok.py} (100%) rename python/{test-data/testLiteralInstanceOf.err => test-data-2.0/extras/testIterator_ok.err} (100%) rename python/{test-data/testIterator2.out => test-data-2.0/extras/testIterator_ok.out} (100%) rename python/{test-data/testIterator.py => test-data-2.0/extras/testIterator_ok.py} (100%) rename python/{test-data/testNameErrorBug.err => test-data-2.0/extras/testLiteralInstanceOf_ok.err} (100%) rename python/{test-data/testLiteralInstanceOf.out => test-data-2.0/extras/testLiteralInstanceOf_ok.out} (100%) rename python/{test-data/testLiteralInstanceOf.py => test-data-2.0/extras/testLiteralInstanceOf_ok.py} (100%) rename python/{test-data/testNameErrorBug.out => test-data-2.0/extras/testNameErrorBug_ok.err} (100%) rename python/{test-data/testTypeKeyword.err => test-data-2.0/extras/testNameErrorBug_ok.out} (100%) rename python/{test-data/testNameErrorBug.py => test-data-2.0/extras/testNameErrorBug_ok.py} (100%) rename python/{test-data/testTypes2.err-notypes => test-data-2.0/extras/testTypeKeyword_ok.err} (100%) rename python/{test-data/testTypeKeyword.out => test-data-2.0/extras/testTypeKeyword_ok.out} (100%) rename python/{test-data/testTypeKeyword.py => test-data-2.0/extras/testTypeKeyword_ok.py} (82%) create mode 100644 python/test-data-2.0/extras/testTypes2.err rename python/{test-data/testTypes2.out => test-data-2.0/extras/testTypes2.err-notypes} (100%) rename python/{test-data/testTypesCollections3.out => test-data-2.0/extras/testTypes2.out} (100%) rename python/{test-data => test-data-2.0/extras}/testTypes2.py (100%) create mode 100644 python/test-data-2.0/extras/testTypes2_ok.py rename python/{test-data/testTypesCollections4.out => test-data-2.0/extras/testTypesHigherOrderFuns2_ok.err} (100%) rename python/{test-data/testTypesHigherOrderFuns2.out => test-data-2.0/extras/testTypesHigherOrderFuns2_ok.out} (100%) rename python/{test-data/testTypesHigherOrderFuns2.py => test-data-2.0/extras/testTypesHigherOrderFuns2_ok.py} (100%) rename python/{test-data/testTypesHigherOrderFuns2.err => test-data-2.0/extras/testTypesHigherOrderFuns4_ok.err} (100%) rename python/{test-data/testTypesHigherOrderFuns4.out => test-data-2.0/extras/testTypesHigherOrderFuns4_ok.out} (100%) rename python/{test-data/testTypesHigherOrderFuns4.py => test-data-2.0/extras/testTypesHigherOrderFuns4_ok.py} (100%) rename python/{test-data/testTypesHigherOrderFuns4.err => test-data-2.0/extras/testTypesHigherOrderFuns5_ok.err} (100%) rename python/{test-data/testTypesHigherOrderFuns5.out => test-data-2.0/extras/testTypesHigherOrderFuns5_ok.out} (100%) rename python/{test-data/testTypesHigherOrderFuns5.py => test-data-2.0/extras/testTypesHigherOrderFuns5_ok.py} (100%) rename python/{test-data/testTypesHigherOrderFuns5.err => test-data-2.0/extras/testTypesProtos5_ok.err} (100%) rename python/{test-data/testTypesProtos5.out => test-data-2.0/extras/testTypesProtos5_ok.out} (100%) rename python/{test-data/testTypesProtos5.py => test-data-2.0/extras/testTypesProtos5_ok.py} (100%) rename python/{test-data/testTypesProtos5.err => test-data-2.0/extras/testTypesWrapperEq_ok.err} (100%) rename python/{test-data/testTypesWrapperEq.out => test-data-2.0/extras/testTypesWrapperEq_ok.out} (100%) rename python/{test-data/testTypesWrapperEq.py => test-data-2.0/extras/testTypesWrapperEq_ok.py} (100%) rename python/{test-data/testTypesWrapperEq.err => test-data-2.0/extras/testUnion2_ok.err} (100%) rename python/{test-data/testUnion2.out => test-data-2.0/extras/testUnion2_ok.out} (100%) rename python/{test-data/testUnion2.py => test-data-2.0/extras/testUnion2_ok.py} (100%) rename python/{test-data/testUnion.err => test-data-2.0/extras/testUnion3_ok.err} (100%) rename python/{test-data/testUnion3.out => test-data-2.0/extras/testUnion3_ok.out} (100%) rename python/{test-data/testUnion3.py => test-data-2.0/extras/testUnion3_ok.py} (100%) rename python/{test-data/testUnion2.err => test-data-2.0/extras/testUnionLiteral_ok.err} (100%) rename python/{test-data/testUnion3.err => test-data-2.0/extras/testUnionLiteral_ok.out} (100%) rename python/{test-data/testUnionLiteral.py => test-data-2.0/extras/testUnionLiteral_ok.py} (100%) rename python/{test-data/testUnionLiteral.err => test-data-2.0/extras/testUnionOfUnion_ok.err} (100%) rename python/{test-data/testUnionOfUnion.out => test-data-2.0/extras/testUnionOfUnion_ok.out} (100%) rename python/{test-data/testUnionOfUnion.py => test-data-2.0/extras/testUnionOfUnion_ok.py} (100%) rename python/{test-data/testUnionLiteral.out => test-data-2.0/extras/testUnion_ok.err} (100%) rename python/{test-data/testUnion.out => test-data-2.0/extras/testUnion_ok.out} (100%) rename python/{test-data/testUnion.py => test-data-2.0/extras/testUnion_ok.py} (100%) delete mode 100644 python/test-data/printModuleNameImport.py create mode 100644 python/test-data/testHintParentheses4.py create mode 100644 python/test-data/testHintParentheses5.py create mode 100644 python/test-data/testHintParentheses6.py delete mode 100644 python/test-data/testTypes2.err delete mode 100644 python/test-data/testTypesCollections3.err delete mode 100644 python/test-data/testTypesCollections3.py delete mode 100644 python/test-data/testTypesCollections4.err delete mode 100644 python/test-data/testTypesCollections4.py rename python/test-data/{testTypesDict1.py => testTypesDict1_ok.py} (100%) rename python/test-data/{testTypesSet1.py => testTypesSet1_ok.py} (100%) delete mode 100644 python/test-data/testUnionOfUnion.err delete mode 100644 python/test-data/testWrap.err delete mode 100644 python/test-data/testWrap.out delete mode 100644 python/test-data/testWrap.py delete mode 100644 python/test-data/testWrap2.err delete mode 100644 python/test-data/testWrap2.out delete mode 100644 python/test-data/testWrap2.py delete mode 100644 python/test-data/testWrapperError.err delete mode 100644 python/test-data/testWrapperError.out delete mode 100644 python/test-data/testWrapperError.py diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index cf8ae9e1..828e903c 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -1,12 +1,13 @@ from pathlib import Path from fileTestsLib import * -directory = Path("test-data-2.0") +directories = [Path("test-data-2.0/basics"), Path("test-data-2.0/extras")] -for file in directory.iterdir(): - if file.is_file(): - name = file.as_posix() - if name.endswith('.py'): - check(name) +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 index 90043f82..67587271 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -1,3 +1,4 @@ +from __future__ import annotations from dataclasses import dataclass import os from typing import * @@ -7,6 +8,8 @@ import argparse import re import shutil +import json +import re GLOBAL_CHECK_OUTPUTS = True @@ -309,7 +312,38 @@ def _check(testFile: str, def guessExitCode(testFile: str) -> int: return 0 if testFile.endswith('_ok.py') else 1 -def check(testFile: str, +_CONFIG_RE = re.compile(r'^# WYPP_TEST_CONFIG:\s*(\{.*\})\s*$') + +@dataclass +class WyppTestConfig: + typecheck: Literal[True, False, "both"] + @staticmethod + def default() -> WyppTestConfig: + return WyppTestConfig(typecheck=True) + +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'] + 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}') + return WyppTestConfig(typecheck=j['typecheck']) + return WyppTestConfig.default() + +def checkNoConfig(testFile: str, exitCode: int = 1, typecheck: bool = True, args: list[str] = [], @@ -326,6 +360,26 @@ def check(testFile: str, if not ctx.opts.keepGoing: ctx.results.finish() +def check(testFile: str, + exitCode: int = 1, + args: list[str] = [], + pythonPath: list[str] = [], + minVersion: Optional[tuple[int, int]] = None, + checkOutputs: bool = True, + ctx: TestContext = globalCtx,): + cfg = readWyppTestConfig(testFile) + 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: + checkNoConfig(testFile, exitCode, typecheck=cfg.typecheck, args=args, + pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs, + ctx=ctx, what=' (no typecheck)') + def checkBoth(testFile: str, exitCode: int = 1, args: list[str] = [], diff --git a/python/src/__init__.py b/python/src/__init__.py index c55843d2..47c68aa8 100644 --- a/python/src/__init__.py +++ b/python/src/__init__.py @@ -72,3 +72,4 @@ printTestResults = w.printTestResults resetTestCount = w.resetTestCount deepEq = w.deepEq +wrapTypecheck = w.wrapTypecheck diff --git a/python/src/instrument.py b/python/src/instrument.py index fed712d2..8fd0ea05 100644 --- a/python/src/instrument.py +++ b/python/src/instrument.py @@ -10,6 +10,7 @@ from os import PathLike import utils from myLogging import * +from contextlib import contextmanager def parseExp(s: str) -> ast.expr: match ast.parse(s): @@ -36,10 +37,21 @@ def transformStmt(stmt: ast.stmt, outerClassName: Optional[str]) -> ast.stmt: 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) -> ast.Module | ast.Expression | ast.Interactive: match m: case ast.Module(body, type_ignores): newStmts = [transformStmt(stmt, outerClassName=None) 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 @@ -71,9 +83,10 @@ def source_to_code( return code class InstrumentingFinder(importlib.abc.MetaPathFinder): - def __init__(self, finder, modDir): + 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, @@ -85,19 +98,40 @@ def find_spec( if spec is None: return None origin = os.path.realpath(spec.origin) - isLocalModule = origin.startswith(self.modDir) + 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 -def setupFinder(modDir: str): - for finder in sys.meta_path: - if ( - isinstance(finder, type) - and finder.__name__ == "PathFinder" - and hasattr(finder, "find_spec") - ): - break +@contextmanager +def setupFinder(modDir: str, extraDirs: list[str], typechecking: bool): + if not typechecking: + yield else: - raise RuntimeError("Cannot find a PathFinder in sys.meta_path") - sys.meta_path.insert(0, InstrumentingFinder(finder, modDir)) + # 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/src/runner.py b/python/src/runner.py index 9927c2d7..7673e597 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -318,14 +318,10 @@ def runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): extraDirs = [] modDir = os.path.dirname(fileToRun) with RunSetup(modDir, [fileToRun] + args): - instrument.setupFinder(modDir) - modName = os.path.basename(os.path.splitext(fileToRun)[0]) - if doTypecheck: - globals['wrapTypecheck'] = typecheck.wrapTypecheck - else: - globals['wrapTypecheck'] = typecheck.wrapNoTypecheck - sys.dont_write_bytecode = True # FIXME: remove - runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=True) + with instrument.setupFinder(modDir, extraDirs, doTypecheck): + modName = os.path.basename(os.path.splitext(fileToRun)[0]) + sys.dont_write_bytecode = True # FIXME: remove + runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=True) def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, extraDirs=extraDirs) diff --git a/python/src/stacktrace.py b/python/src/stacktrace.py index ea7dcc9e..15fe3625 100644 --- a/python/src/stacktrace.py +++ b/python/src/stacktrace.py @@ -5,6 +5,7 @@ from typing import Optional, Any import os import sys +from collections import deque def tbToFrameList(tb: types.TracebackType) -> list[types.FrameType]: cur = tb @@ -15,7 +16,10 @@ def tbToFrameList(tb: types.TracebackType) -> list[types.FrameType]: return res def isCallWithFramesRemoved(frame: types.FrameType): - return frame.f_code.co_name == '_call_with_frames_removed' + 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__' @@ -34,11 +38,22 @@ def limitTraceback(frameList: list[types.FrameType], extraFrames: list[inspect.FrameInfo], filter: bool) -> 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(endIdx - 1, 0, -1): + for i in range(len(frameList)): if isCallWithFramesRemoved(frameList[i]): endIdx = i - 1 - frameList = utils.dropWhile(frameList[:endIdx], lambda f: isWyppFrame(f) or isRunpyFrame(f)) + 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) @@ -52,33 +67,35 @@ def callerOutsideWypp() -> Optional[inspect.FrameInfo]: return None class ReturnTracker: - def __init__(self): - self.__returnFrame: Optional[types.FrameType] = None + 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 # self.__returnFrame = None case 'return': - self.__returnFrame = frame + self.__returnFrames.append(frame) # overwrite oldest when full case 'c_call': pass case 'c_return': pass case 'c_exception': pass - def getReturnFrame(self) -> Optional[inspect.FrameInfo]: - f = self.__returnFrame + def getReturnFrame(self, idx: int) -> Optional[inspect.FrameInfo]: + if idx >= len(self.__returnFrames): + return None + f = self.__returnFrames[idx] if f: tb = inspect.getframeinfo(f, context=1) return inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index) else: return None -def installProfileHook() -> ReturnTracker: +def installProfileHook(entriesToKeep: int) -> ReturnTracker: obj = sys.getprofile() if isinstance(obj, ReturnTracker): return obj - obj = ReturnTracker() + obj = ReturnTracker(entriesToKeep) sys.setprofile(obj) return obj diff --git a/python/src/typecheck.py b/python/src/typecheck.py index afbea0c3..4c905143 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -28,13 +28,7 @@ def isEmptySignature(sig: inspect.Signature) -> bool: def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool: match res: case MatchesTyFailure(exc, ty): - # We want to detect errors such as writing list(int) instead of list[int]. - # Below is a heuristic... - s = str(ty) - if '(' in s: - raise errors.WyppTypeError.invalidType(ty, tyLoc) - else: - raise exc + raise errors.WyppTypeError.invalidType(ty, tyLoc) case b: return b @@ -179,11 +173,14 @@ def _wrap(f: Callable[P, T]) -> Callable[P, T]: info = outerInfo utils._call_with_frames_removed(checkSignature, sig, info, checkCfg) def wrapped(*args, **kwargs) -> T: - returnTracker = stacktrace.installProfileHook() + # when using _call_with_next_frame_removed, we have to take the second-to-last + # return. Hence, we keep the two most recent returns + returnTracker = stacktrace.installProfileHook(2) utils._call_with_frames_removed(checkArguments, sig, args, kwargs, info, checkCfg) - result = f(*args, **kwargs) + result = utils._call_with_next_frame_removed(f, *args, **kwargs) + retFrame = returnTracker.getReturnFrame(0) utils._call_with_frames_removed( - checkReturn, sig, returnTracker.getReturnFrame(), result, info, checkCfg + checkReturn, sig, retFrame, result, info, checkCfg ) return result return wrapped diff --git a/python/src/utils.py b/python/src/utils.py index 8dffb1df..84cb309e 100644 --- a/python/src/utils.py +++ b/python/src/utils.py @@ -5,12 +5,20 @@ P = ParamSpec("P") T = TypeVar("T") -# The name of this function is magical +# 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: @@ -28,6 +36,27 @@ def dropWhile(l: list, f: Callable[[Any], bool]) -> list: break 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' diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index dc504af9..d5532164 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -3,7 +3,6 @@ import inspect import errors import typecheck -from utils import _call_with_frames_removed import records _DEBUG = False @@ -314,4 +313,4 @@ def impossible(msg=None): math = moduleMath - +wrapTypecheck = typecheck.wrapTypecheck diff --git a/python/test-data-2.0/constructor.err b/python/test-data-2.0/basics/constructor.err similarity index 100% rename from python/test-data-2.0/constructor.err rename to python/test-data-2.0/basics/constructor.err diff --git a/python/test-data-2.0/constructor.err_en b/python/test-data-2.0/basics/constructor.err_en similarity index 100% rename from python/test-data-2.0/constructor.err_en rename to python/test-data-2.0/basics/constructor.err_en diff --git a/python/test-data-2.0/constructor.out b/python/test-data-2.0/basics/constructor.out similarity index 100% rename from python/test-data-2.0/constructor.out rename to python/test-data-2.0/basics/constructor.out diff --git a/python/test-data-2.0/constructor.out_en b/python/test-data-2.0/basics/constructor.out_en similarity index 100% rename from python/test-data-2.0/constructor.out_en rename to python/test-data-2.0/basics/constructor.out_en diff --git a/python/test-data-2.0/constructor.py b/python/test-data-2.0/basics/constructor.py similarity index 100% rename from python/test-data-2.0/constructor.py rename to python/test-data-2.0/basics/constructor.py diff --git a/python/test-data-2.0/constructorDataclass_ok.py b/python/test-data-2.0/basics/constructorDataclass_ok.py similarity index 100% rename from python/test-data-2.0/constructorDataclass_ok.py rename to python/test-data-2.0/basics/constructorDataclass_ok.py diff --git a/python/test-data-2.0/constructor_ok.err b/python/test-data-2.0/basics/constructor_ok.err similarity index 100% rename from python/test-data-2.0/constructor_ok.err rename to python/test-data-2.0/basics/constructor_ok.err diff --git a/python/test-data-2.0/constructor_ok.out b/python/test-data-2.0/basics/constructor_ok.out similarity index 100% rename from python/test-data-2.0/constructor_ok.out rename to python/test-data-2.0/basics/constructor_ok.out diff --git a/python/test-data-2.0/constructor_ok.py b/python/test-data-2.0/basics/constructor_ok.py similarity index 100% rename from python/test-data-2.0/constructor_ok.py rename to python/test-data-2.0/basics/constructor_ok.py diff --git a/python/test-data-2.0/forwardRefs.err b/python/test-data-2.0/basics/forwardRefs.err similarity index 100% rename from python/test-data-2.0/forwardRefs.err rename to python/test-data-2.0/basics/forwardRefs.err diff --git a/python/test-data-2.0/forwardRefs.err_en b/python/test-data-2.0/basics/forwardRefs.err_en similarity index 100% rename from python/test-data-2.0/forwardRefs.err_en rename to python/test-data-2.0/basics/forwardRefs.err_en diff --git a/python/test-data-2.0/forwardRefs.out b/python/test-data-2.0/basics/forwardRefs.out similarity index 100% rename from python/test-data-2.0/forwardRefs.out rename to python/test-data-2.0/basics/forwardRefs.out diff --git a/python/test-data-2.0/forwardRefs.out_en b/python/test-data-2.0/basics/forwardRefs.out_en similarity index 100% rename from python/test-data-2.0/forwardRefs.out_en rename to python/test-data-2.0/basics/forwardRefs.out_en diff --git a/python/test-data-2.0/forwardRefs.py b/python/test-data-2.0/basics/forwardRefs.py similarity index 100% rename from python/test-data-2.0/forwardRefs.py rename to python/test-data-2.0/basics/forwardRefs.py diff --git a/python/test-data-2.0/forwardRefs_ok.err b/python/test-data-2.0/basics/forwardRefs_ok.err similarity index 100% rename from python/test-data-2.0/forwardRefs_ok.err rename to python/test-data-2.0/basics/forwardRefs_ok.err diff --git a/python/test-data-2.0/forwardRefs_ok.out b/python/test-data-2.0/basics/forwardRefs_ok.out similarity index 100% rename from python/test-data-2.0/forwardRefs_ok.out rename to python/test-data-2.0/basics/forwardRefs_ok.out diff --git a/python/test-data-2.0/forwardRefs_ok.py b/python/test-data-2.0/basics/forwardRefs_ok.py similarity index 100% rename from python/test-data-2.0/forwardRefs_ok.py rename to python/test-data-2.0/basics/forwardRefs_ok.py diff --git a/python/test-data-2.0/functionArg.err b/python/test-data-2.0/basics/functionArg.err similarity index 100% rename from python/test-data-2.0/functionArg.err rename to python/test-data-2.0/basics/functionArg.err diff --git a/python/test-data-2.0/functionArg.err_en b/python/test-data-2.0/basics/functionArg.err_en similarity index 100% rename from python/test-data-2.0/functionArg.err_en rename to python/test-data-2.0/basics/functionArg.err_en diff --git a/python/test-data-2.0/functionArg.out b/python/test-data-2.0/basics/functionArg.out similarity index 100% rename from python/test-data-2.0/functionArg.out rename to python/test-data-2.0/basics/functionArg.out diff --git a/python/test-data-2.0/functionArg.out_en b/python/test-data-2.0/basics/functionArg.out_en similarity index 100% rename from python/test-data-2.0/functionArg.out_en rename to python/test-data-2.0/basics/functionArg.out_en diff --git a/python/test-data-2.0/functionArg.py b/python/test-data-2.0/basics/functionArg.py similarity index 100% rename from python/test-data-2.0/functionArg.py rename to python/test-data-2.0/basics/functionArg.py diff --git a/python/test-data-2.0/functionArg_ok.err b/python/test-data-2.0/basics/functionArg_ok.err similarity index 100% rename from python/test-data-2.0/functionArg_ok.err rename to python/test-data-2.0/basics/functionArg_ok.err diff --git a/python/test-data-2.0/functionArg_ok.err_None b/python/test-data-2.0/basics/functionArg_ok.err_None similarity index 100% rename from python/test-data-2.0/functionArg_ok.err_None rename to python/test-data-2.0/basics/functionArg_ok.err_None diff --git a/python/test-data-2.0/functionArg_ok.out b/python/test-data-2.0/basics/functionArg_ok.out similarity index 100% rename from python/test-data-2.0/functionArg_ok.out rename to python/test-data-2.0/basics/functionArg_ok.out diff --git a/python/test-data-2.0/functionArg_ok.out_None b/python/test-data-2.0/basics/functionArg_ok.out_None similarity index 100% rename from python/test-data-2.0/functionArg_ok.out_None rename to python/test-data-2.0/basics/functionArg_ok.out_None diff --git a/python/test-data-2.0/functionArg_ok.py b/python/test-data-2.0/basics/functionArg_ok.py similarity index 100% rename from python/test-data-2.0/functionArg_ok.py rename to python/test-data-2.0/basics/functionArg_ok.py diff --git a/python/test-data-2.0/functionNoResult.err b/python/test-data-2.0/basics/functionNoResult.err similarity index 100% rename from python/test-data-2.0/functionNoResult.err rename to python/test-data-2.0/basics/functionNoResult.err diff --git a/python/test-data-2.0/functionNoResult.err_en b/python/test-data-2.0/basics/functionNoResult.err_en similarity index 100% rename from python/test-data-2.0/functionNoResult.err_en rename to python/test-data-2.0/basics/functionNoResult.err_en diff --git a/python/test-data-2.0/functionNoResult.out b/python/test-data-2.0/basics/functionNoResult.out similarity index 100% rename from python/test-data-2.0/functionNoResult.out rename to python/test-data-2.0/basics/functionNoResult.out diff --git a/python/test-data-2.0/functionNoResult.out_en b/python/test-data-2.0/basics/functionNoResult.out_en similarity index 100% rename from python/test-data-2.0/functionNoResult.out_en rename to python/test-data-2.0/basics/functionNoResult.out_en diff --git a/python/test-data-2.0/functionNoResult.py b/python/test-data-2.0/basics/functionNoResult.py similarity index 100% rename from python/test-data-2.0/functionNoResult.py rename to python/test-data-2.0/basics/functionNoResult.py diff --git a/python/test-data-2.0/functionNoResult2.err b/python/test-data-2.0/basics/functionNoResult2.err similarity index 100% rename from python/test-data-2.0/functionNoResult2.err rename to python/test-data-2.0/basics/functionNoResult2.err diff --git a/python/test-data-2.0/functionNoResult2.err_en b/python/test-data-2.0/basics/functionNoResult2.err_en similarity index 100% rename from python/test-data-2.0/functionNoResult2.err_en rename to python/test-data-2.0/basics/functionNoResult2.err_en diff --git a/python/test-data-2.0/functionNoResult2.out b/python/test-data-2.0/basics/functionNoResult2.out similarity index 100% rename from python/test-data-2.0/functionNoResult2.out rename to python/test-data-2.0/basics/functionNoResult2.out diff --git a/python/test-data-2.0/functionNoResult2.out_en b/python/test-data-2.0/basics/functionNoResult2.out_en similarity index 100% rename from python/test-data-2.0/functionNoResult2.out_en rename to python/test-data-2.0/basics/functionNoResult2.out_en diff --git a/python/test-data-2.0/functionNoResult2.py b/python/test-data-2.0/basics/functionNoResult2.py similarity index 100% rename from python/test-data-2.0/functionNoResult2.py rename to python/test-data-2.0/basics/functionNoResult2.py diff --git a/python/test-data-2.0/functionNoResult2_ok.err b/python/test-data-2.0/basics/functionNoResult2_ok.err similarity index 100% rename from python/test-data-2.0/functionNoResult2_ok.err rename to python/test-data-2.0/basics/functionNoResult2_ok.err diff --git a/python/test-data-2.0/functionNoResult2_ok.out b/python/test-data-2.0/basics/functionNoResult2_ok.out similarity index 100% rename from python/test-data-2.0/functionNoResult2_ok.out rename to python/test-data-2.0/basics/functionNoResult2_ok.out diff --git a/python/test-data-2.0/functionNoResult2_ok.py b/python/test-data-2.0/basics/functionNoResult2_ok.py similarity index 100% rename from python/test-data-2.0/functionNoResult2_ok.py rename to python/test-data-2.0/basics/functionNoResult2_ok.py diff --git a/python/test-data-2.0/functionNoResult3.err b/python/test-data-2.0/basics/functionNoResult3.err similarity index 100% rename from python/test-data-2.0/functionNoResult3.err rename to python/test-data-2.0/basics/functionNoResult3.err diff --git a/python/test-data-2.0/functionNoResult3.err_en b/python/test-data-2.0/basics/functionNoResult3.err_en similarity index 100% rename from python/test-data-2.0/functionNoResult3.err_en rename to python/test-data-2.0/basics/functionNoResult3.err_en diff --git a/python/test-data-2.0/functionNoResult3.out b/python/test-data-2.0/basics/functionNoResult3.out similarity index 100% rename from python/test-data-2.0/functionNoResult3.out rename to python/test-data-2.0/basics/functionNoResult3.out diff --git a/python/test-data-2.0/functionNoResult3.out_en b/python/test-data-2.0/basics/functionNoResult3.out_en similarity index 100% rename from python/test-data-2.0/functionNoResult3.out_en rename to python/test-data-2.0/basics/functionNoResult3.out_en diff --git a/python/test-data-2.0/functionNoResult3.py b/python/test-data-2.0/basics/functionNoResult3.py similarity index 100% rename from python/test-data-2.0/functionNoResult3.py rename to python/test-data-2.0/basics/functionNoResult3.py diff --git a/python/test-data-2.0/functionNoResult3_ok.err b/python/test-data-2.0/basics/functionNoResult3_ok.err similarity index 100% rename from python/test-data-2.0/functionNoResult3_ok.err rename to python/test-data-2.0/basics/functionNoResult3_ok.err diff --git a/python/test-data-2.0/functionNoResult3_ok.out b/python/test-data-2.0/basics/functionNoResult3_ok.out similarity index 100% rename from python/test-data-2.0/functionNoResult3_ok.out rename to python/test-data-2.0/basics/functionNoResult3_ok.out diff --git a/python/test-data-2.0/functionNoResult3_ok.py b/python/test-data-2.0/basics/functionNoResult3_ok.py similarity index 100% rename from python/test-data-2.0/functionNoResult3_ok.py rename to python/test-data-2.0/basics/functionNoResult3_ok.py diff --git a/python/test-data-2.0/functionNoResult_ok.err b/python/test-data-2.0/basics/functionNoResult_ok.err similarity index 100% rename from python/test-data-2.0/functionNoResult_ok.err rename to python/test-data-2.0/basics/functionNoResult_ok.err diff --git a/python/test-data-2.0/functionNoResult_ok.out b/python/test-data-2.0/basics/functionNoResult_ok.out similarity index 100% rename from python/test-data-2.0/functionNoResult_ok.out rename to python/test-data-2.0/basics/functionNoResult_ok.out diff --git a/python/test-data-2.0/functionNoResult_ok.py b/python/test-data-2.0/basics/functionNoResult_ok.py similarity index 100% rename from python/test-data-2.0/functionNoResult_ok.py rename to python/test-data-2.0/basics/functionNoResult_ok.py diff --git a/python/test-data-2.0/functionResult.err b/python/test-data-2.0/basics/functionResult.err similarity index 100% rename from python/test-data-2.0/functionResult.err rename to python/test-data-2.0/basics/functionResult.err diff --git a/python/test-data-2.0/functionResult.err_en b/python/test-data-2.0/basics/functionResult.err_en similarity index 100% rename from python/test-data-2.0/functionResult.err_en rename to python/test-data-2.0/basics/functionResult.err_en diff --git a/python/test-data-2.0/functionResult.out b/python/test-data-2.0/basics/functionResult.out similarity index 100% rename from python/test-data-2.0/functionResult.out rename to python/test-data-2.0/basics/functionResult.out diff --git a/python/test-data-2.0/functionResult.out_en b/python/test-data-2.0/basics/functionResult.out_en similarity index 100% rename from python/test-data-2.0/functionResult.out_en rename to python/test-data-2.0/basics/functionResult.out_en diff --git a/python/test-data-2.0/functionResult.py b/python/test-data-2.0/basics/functionResult.py similarity index 100% rename from python/test-data-2.0/functionResult.py rename to python/test-data-2.0/basics/functionResult.py diff --git a/python/test-data-2.0/functionResult2.err b/python/test-data-2.0/basics/functionResult2.err similarity index 100% rename from python/test-data-2.0/functionResult2.err rename to python/test-data-2.0/basics/functionResult2.err diff --git a/python/test-data-2.0/functionResult2.err_en b/python/test-data-2.0/basics/functionResult2.err_en similarity index 100% rename from python/test-data-2.0/functionResult2.err_en rename to python/test-data-2.0/basics/functionResult2.err_en diff --git a/python/test-data-2.0/functionResult2.out b/python/test-data-2.0/basics/functionResult2.out similarity index 100% rename from python/test-data-2.0/functionResult2.out rename to python/test-data-2.0/basics/functionResult2.out diff --git a/python/test-data-2.0/functionResult2.out_en b/python/test-data-2.0/basics/functionResult2.out_en similarity index 100% rename from python/test-data-2.0/functionResult2.out_en rename to python/test-data-2.0/basics/functionResult2.out_en diff --git a/python/test-data-2.0/functionResult2.py b/python/test-data-2.0/basics/functionResult2.py similarity index 100% rename from python/test-data-2.0/functionResult2.py rename to python/test-data-2.0/basics/functionResult2.py diff --git a/python/test-data-2.0/functionResult_ok.err b/python/test-data-2.0/basics/functionResult_ok.err similarity index 100% rename from python/test-data-2.0/functionResult_ok.err rename to python/test-data-2.0/basics/functionResult_ok.err diff --git a/python/test-data-2.0/functionResult_ok.out b/python/test-data-2.0/basics/functionResult_ok.out similarity index 100% rename from python/test-data-2.0/functionResult_ok.out rename to python/test-data-2.0/basics/functionResult_ok.out diff --git a/python/test-data-2.0/functionResult_ok.py b/python/test-data-2.0/basics/functionResult_ok.py similarity index 100% rename from python/test-data-2.0/functionResult_ok.py rename to python/test-data-2.0/basics/functionResult_ok.py diff --git a/python/test-data-2.0/iterator.err b/python/test-data-2.0/basics/iterator.err similarity index 100% rename from python/test-data-2.0/iterator.err rename to python/test-data-2.0/basics/iterator.err diff --git a/python/test-data-2.0/iterator.err_en b/python/test-data-2.0/basics/iterator.err_en similarity index 100% rename from python/test-data-2.0/iterator.err_en rename to python/test-data-2.0/basics/iterator.err_en diff --git a/python/test-data-2.0/iterator.out b/python/test-data-2.0/basics/iterator.out similarity index 100% rename from python/test-data-2.0/iterator.out rename to python/test-data-2.0/basics/iterator.out diff --git a/python/test-data-2.0/iterator.out_en b/python/test-data-2.0/basics/iterator.out_en similarity index 100% rename from python/test-data-2.0/iterator.out_en rename to python/test-data-2.0/basics/iterator.out_en diff --git a/python/test-data-2.0/iterator.py b/python/test-data-2.0/basics/iterator.py similarity index 100% rename from python/test-data-2.0/iterator.py rename to python/test-data-2.0/basics/iterator.py diff --git a/python/test-data-2.0/iterator_ok.err b/python/test-data-2.0/basics/iterator_ok.err similarity index 100% rename from python/test-data-2.0/iterator_ok.err rename to python/test-data-2.0/basics/iterator_ok.err diff --git a/python/test-data-2.0/iterator_ok.out b/python/test-data-2.0/basics/iterator_ok.out similarity index 100% rename from python/test-data-2.0/iterator_ok.out rename to python/test-data-2.0/basics/iterator_ok.out diff --git a/python/test-data-2.0/iterator_ok.py b/python/test-data-2.0/basics/iterator_ok.py similarity index 100% rename from python/test-data-2.0/iterator_ok.py rename to python/test-data-2.0/basics/iterator_ok.py diff --git a/python/test-data-2.0/listArg.err b/python/test-data-2.0/basics/listArg.err similarity index 100% rename from python/test-data-2.0/listArg.err rename to python/test-data-2.0/basics/listArg.err diff --git a/python/test-data-2.0/listArg.err_en b/python/test-data-2.0/basics/listArg.err_en similarity index 100% rename from python/test-data-2.0/listArg.err_en rename to python/test-data-2.0/basics/listArg.err_en diff --git a/python/test-data-2.0/listArg.out b/python/test-data-2.0/basics/listArg.out similarity index 100% rename from python/test-data-2.0/listArg.out rename to python/test-data-2.0/basics/listArg.out diff --git a/python/test-data-2.0/listArg.out_en b/python/test-data-2.0/basics/listArg.out_en similarity index 100% rename from python/test-data-2.0/listArg.out_en rename to python/test-data-2.0/basics/listArg.out_en diff --git a/python/test-data-2.0/listArg.py b/python/test-data-2.0/basics/listArg.py similarity index 100% rename from python/test-data-2.0/listArg.py rename to python/test-data-2.0/basics/listArg.py diff --git a/python/test-data-2.0/listArg_ok.err b/python/test-data-2.0/basics/listArg_ok.err similarity index 100% rename from python/test-data-2.0/listArg_ok.err rename to python/test-data-2.0/basics/listArg_ok.err diff --git a/python/test-data-2.0/listArg_ok.out b/python/test-data-2.0/basics/listArg_ok.out similarity index 100% rename from python/test-data-2.0/listArg_ok.out rename to python/test-data-2.0/basics/listArg_ok.out diff --git a/python/test-data-2.0/listArg_ok.py b/python/test-data-2.0/basics/listArg_ok.py similarity index 100% rename from python/test-data-2.0/listArg_ok.py rename to python/test-data-2.0/basics/listArg_ok.py diff --git a/python/test-data-2.0/listResult.err b/python/test-data-2.0/basics/listResult.err similarity index 100% rename from python/test-data-2.0/listResult.err rename to python/test-data-2.0/basics/listResult.err diff --git a/python/test-data-2.0/listResult.err_en b/python/test-data-2.0/basics/listResult.err_en similarity index 100% rename from python/test-data-2.0/listResult.err_en rename to python/test-data-2.0/basics/listResult.err_en diff --git a/python/test-data-2.0/listResult.out b/python/test-data-2.0/basics/listResult.out similarity index 100% rename from python/test-data-2.0/listResult.out rename to python/test-data-2.0/basics/listResult.out diff --git a/python/test-data-2.0/listResult.out_en b/python/test-data-2.0/basics/listResult.out_en similarity index 100% rename from python/test-data-2.0/listResult.out_en rename to python/test-data-2.0/basics/listResult.out_en diff --git a/python/test-data-2.0/listResult.py b/python/test-data-2.0/basics/listResult.py similarity index 100% rename from python/test-data-2.0/listResult.py rename to python/test-data-2.0/basics/listResult.py diff --git a/python/test-data-2.0/listResult_ok.err b/python/test-data-2.0/basics/listResult_ok.err similarity index 100% rename from python/test-data-2.0/listResult_ok.err rename to python/test-data-2.0/basics/listResult_ok.err diff --git a/python/test-data-2.0/listResult_ok.out b/python/test-data-2.0/basics/listResult_ok.out similarity index 100% rename from python/test-data-2.0/listResult_ok.out rename to python/test-data-2.0/basics/listResult_ok.out diff --git a/python/test-data-2.0/listResult_ok.py b/python/test-data-2.0/basics/listResult_ok.py similarity index 100% rename from python/test-data-2.0/listResult_ok.py rename to python/test-data-2.0/basics/listResult_ok.py diff --git a/python/test-data-2.0/method.err b/python/test-data-2.0/basics/method.err similarity index 100% rename from python/test-data-2.0/method.err rename to python/test-data-2.0/basics/method.err diff --git a/python/test-data-2.0/method.err_en b/python/test-data-2.0/basics/method.err_en similarity index 100% rename from python/test-data-2.0/method.err_en rename to python/test-data-2.0/basics/method.err_en diff --git a/python/test-data-2.0/method.out b/python/test-data-2.0/basics/method.out similarity index 100% rename from python/test-data-2.0/method.out rename to python/test-data-2.0/basics/method.out diff --git a/python/test-data-2.0/method.out_en b/python/test-data-2.0/basics/method.out_en similarity index 100% rename from python/test-data-2.0/method.out_en rename to python/test-data-2.0/basics/method.out_en diff --git a/python/test-data-2.0/method.py b/python/test-data-2.0/basics/method.py similarity index 100% rename from python/test-data-2.0/method.py rename to python/test-data-2.0/basics/method.py diff --git a/python/test-data-2.0/method_ok.err b/python/test-data-2.0/basics/method_ok.err similarity index 100% rename from python/test-data-2.0/method_ok.err rename to python/test-data-2.0/basics/method_ok.err diff --git a/python/test-data-2.0/method_ok.out b/python/test-data-2.0/basics/method_ok.out similarity index 100% rename from python/test-data-2.0/method_ok.out rename to python/test-data-2.0/basics/method_ok.out diff --git a/python/test-data-2.0/method_ok.py b/python/test-data-2.0/basics/method_ok.py similarity index 100% rename from python/test-data-2.0/method_ok.py rename to python/test-data-2.0/basics/method_ok.py diff --git a/python/test-data-2.0/mutable.err b/python/test-data-2.0/basics/mutable.err similarity index 100% rename from python/test-data-2.0/mutable.err rename to python/test-data-2.0/basics/mutable.err diff --git a/python/test-data-2.0/mutable.err_en b/python/test-data-2.0/basics/mutable.err_en similarity index 100% rename from python/test-data-2.0/mutable.err_en rename to python/test-data-2.0/basics/mutable.err_en diff --git a/python/test-data-2.0/mutable.out b/python/test-data-2.0/basics/mutable.out similarity index 100% rename from python/test-data-2.0/mutable.out rename to python/test-data-2.0/basics/mutable.out diff --git a/python/test-data-2.0/mutable.out_en b/python/test-data-2.0/basics/mutable.out_en similarity index 100% rename from python/test-data-2.0/mutable.out_en rename to python/test-data-2.0/basics/mutable.out_en diff --git a/python/test-data-2.0/mutable.py b/python/test-data-2.0/basics/mutable.py similarity index 100% rename from python/test-data-2.0/mutable.py rename to python/test-data-2.0/basics/mutable.py diff --git a/python/test-data-2.0/mutable2.err b/python/test-data-2.0/basics/mutable2.err similarity index 75% rename from python/test-data-2.0/mutable2.err rename to python/test-data-2.0/basics/mutable2.err index ca503b65..12014cde 100644 --- a/python/test-data-2.0/mutable2.err +++ b/python/test-data-2.0/basics/mutable2.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: '2' -Der Aufruf des Konstruktors der Klasse `Point` erwartet Wert vom Typ `int` als zweites Argument. +Der Aufruf des Konstruktors des Records `Point` erwartet Wert vom Typ `int` als zweites Argument. Aber der übergebene Wert hat Typ `str`. ## Datei mutable2.py diff --git a/python/test-data-2.0/mutable2.err_en b/python/test-data-2.0/basics/mutable2.err_en similarity index 76% rename from python/test-data-2.0/mutable2.err_en rename to python/test-data-2.0/basics/mutable2.err_en index 3cc36974..fa5df34e 100644 --- a/python/test-data-2.0/mutable2.err_en +++ b/python/test-data-2.0/basics/mutable2.err_en @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: '2' -The call of the constructor of class `Point` expects value of type `int` as 2nd argument. +The call of the constructor of record `Point` expects value of type `int` as 2nd argument. But the value given has type `str`. ## File mutable2.py diff --git a/python/test-data-2.0/mutable2.out b/python/test-data-2.0/basics/mutable2.out similarity index 100% rename from python/test-data-2.0/mutable2.out rename to python/test-data-2.0/basics/mutable2.out diff --git a/python/test-data-2.0/mutable2.out_en b/python/test-data-2.0/basics/mutable2.out_en similarity index 100% rename from python/test-data-2.0/mutable2.out_en rename to python/test-data-2.0/basics/mutable2.out_en diff --git a/python/test-data-2.0/mutable2.py b/python/test-data-2.0/basics/mutable2.py similarity index 100% rename from python/test-data-2.0/mutable2.py rename to python/test-data-2.0/basics/mutable2.py diff --git a/python/test-data-2.0/mutable2_ok.err b/python/test-data-2.0/basics/mutable2_ok.err similarity index 100% rename from python/test-data-2.0/mutable2_ok.err rename to python/test-data-2.0/basics/mutable2_ok.err diff --git a/python/test-data-2.0/mutable2_ok.out b/python/test-data-2.0/basics/mutable2_ok.out similarity index 100% rename from python/test-data-2.0/mutable2_ok.out rename to python/test-data-2.0/basics/mutable2_ok.out diff --git a/python/test-data-2.0/mutable2_ok.py b/python/test-data-2.0/basics/mutable2_ok.py similarity index 100% rename from python/test-data-2.0/mutable2_ok.py rename to python/test-data-2.0/basics/mutable2_ok.py diff --git a/python/test-data-2.0/mutable_ok.err b/python/test-data-2.0/basics/mutable_ok.err similarity index 100% rename from python/test-data-2.0/mutable_ok.err rename to python/test-data-2.0/basics/mutable_ok.err diff --git a/python/test-data-2.0/mutable_ok.out b/python/test-data-2.0/basics/mutable_ok.out similarity index 100% rename from python/test-data-2.0/mutable_ok.out rename to python/test-data-2.0/basics/mutable_ok.out diff --git a/python/test-data-2.0/mutable_ok.py b/python/test-data-2.0/basics/mutable_ok.py similarity index 100% rename from python/test-data-2.0/mutable_ok.py rename to python/test-data-2.0/basics/mutable_ok.py diff --git a/python/test-data-2.0/nested.err b/python/test-data-2.0/basics/nested.err similarity index 100% rename from python/test-data-2.0/nested.err rename to python/test-data-2.0/basics/nested.err diff --git a/python/test-data-2.0/nested.out b/python/test-data-2.0/basics/nested.out similarity index 100% rename from python/test-data-2.0/nested.out rename to python/test-data-2.0/basics/nested.out diff --git a/python/test-data-2.0/nested.py b/python/test-data-2.0/basics/nested.py similarity index 100% rename from python/test-data-2.0/nested.py rename to python/test-data-2.0/basics/nested.py diff --git a/python/test-data-2.0/nosig_ok.err b/python/test-data-2.0/basics/nosig_ok.err similarity index 100% rename from python/test-data-2.0/nosig_ok.err rename to python/test-data-2.0/basics/nosig_ok.err diff --git a/python/test-data-2.0/nosig_ok.out b/python/test-data-2.0/basics/nosig_ok.out similarity index 100% rename from python/test-data-2.0/nosig_ok.out rename to python/test-data-2.0/basics/nosig_ok.out diff --git a/python/test-data-2.0/nosig_ok.py b/python/test-data-2.0/basics/nosig_ok.py similarity index 100% rename from python/test-data-2.0/nosig_ok.py rename to python/test-data-2.0/basics/nosig_ok.py diff --git a/python/test-data-2.0/optionalArgs.err b/python/test-data-2.0/basics/optionalArgs.err similarity index 100% rename from python/test-data-2.0/optionalArgs.err rename to python/test-data-2.0/basics/optionalArgs.err diff --git a/python/test-data-2.0/optionalArgs.err_en b/python/test-data-2.0/basics/optionalArgs.err_en similarity index 100% rename from python/test-data-2.0/optionalArgs.err_en rename to python/test-data-2.0/basics/optionalArgs.err_en diff --git a/python/test-data-2.0/optionalArgs.out b/python/test-data-2.0/basics/optionalArgs.out similarity index 100% rename from python/test-data-2.0/optionalArgs.out rename to python/test-data-2.0/basics/optionalArgs.out diff --git a/python/test-data-2.0/optionalArgs.out_en b/python/test-data-2.0/basics/optionalArgs.out_en similarity index 100% rename from python/test-data-2.0/optionalArgs.out_en rename to python/test-data-2.0/basics/optionalArgs.out_en diff --git a/python/test-data-2.0/optionalArgs.py b/python/test-data-2.0/basics/optionalArgs.py similarity index 100% rename from python/test-data-2.0/optionalArgs.py rename to python/test-data-2.0/basics/optionalArgs.py diff --git a/python/test-data-2.0/optionalArgs2.err b/python/test-data-2.0/basics/optionalArgs2.err similarity index 100% rename from python/test-data-2.0/optionalArgs2.err rename to python/test-data-2.0/basics/optionalArgs2.err diff --git a/python/test-data-2.0/optionalArgs2.err_en b/python/test-data-2.0/basics/optionalArgs2.err_en similarity index 100% rename from python/test-data-2.0/optionalArgs2.err_en rename to python/test-data-2.0/basics/optionalArgs2.err_en diff --git a/python/test-data-2.0/optionalArgs2.out b/python/test-data-2.0/basics/optionalArgs2.out similarity index 100% rename from python/test-data-2.0/optionalArgs2.out rename to python/test-data-2.0/basics/optionalArgs2.out diff --git a/python/test-data-2.0/optionalArgs2.out_en b/python/test-data-2.0/basics/optionalArgs2.out_en similarity index 100% rename from python/test-data-2.0/optionalArgs2.out_en rename to python/test-data-2.0/basics/optionalArgs2.out_en diff --git a/python/test-data-2.0/optionalArgs2.py b/python/test-data-2.0/basics/optionalArgs2.py similarity index 100% rename from python/test-data-2.0/optionalArgs2.py rename to python/test-data-2.0/basics/optionalArgs2.py diff --git a/python/test-data-2.0/optionalArgs3.err b/python/test-data-2.0/basics/optionalArgs3.err similarity index 100% rename from python/test-data-2.0/optionalArgs3.err rename to python/test-data-2.0/basics/optionalArgs3.err diff --git a/python/test-data-2.0/optionalArgs3.err_en b/python/test-data-2.0/basics/optionalArgs3.err_en similarity index 100% rename from python/test-data-2.0/optionalArgs3.err_en rename to python/test-data-2.0/basics/optionalArgs3.err_en diff --git a/python/test-data-2.0/optionalArgs3.out b/python/test-data-2.0/basics/optionalArgs3.out similarity index 100% rename from python/test-data-2.0/optionalArgs3.out rename to python/test-data-2.0/basics/optionalArgs3.out diff --git a/python/test-data-2.0/optionalArgs3.out_en b/python/test-data-2.0/basics/optionalArgs3.out_en similarity index 100% rename from python/test-data-2.0/optionalArgs3.out_en rename to python/test-data-2.0/basics/optionalArgs3.out_en diff --git a/python/test-data-2.0/optionalArgs3.py b/python/test-data-2.0/basics/optionalArgs3.py similarity index 100% rename from python/test-data-2.0/optionalArgs3.py rename to python/test-data-2.0/basics/optionalArgs3.py diff --git a/python/test-data-2.0/optionalArgs4.err b/python/test-data-2.0/basics/optionalArgs4.err similarity index 100% rename from python/test-data-2.0/optionalArgs4.err rename to python/test-data-2.0/basics/optionalArgs4.err diff --git a/python/test-data-2.0/optionalArgs4.err_en b/python/test-data-2.0/basics/optionalArgs4.err_en similarity index 100% rename from python/test-data-2.0/optionalArgs4.err_en rename to python/test-data-2.0/basics/optionalArgs4.err_en diff --git a/python/test-data-2.0/optionalArgs4.out b/python/test-data-2.0/basics/optionalArgs4.out similarity index 100% rename from python/test-data-2.0/optionalArgs4.out rename to python/test-data-2.0/basics/optionalArgs4.out diff --git a/python/test-data-2.0/optionalArgs4.out_en b/python/test-data-2.0/basics/optionalArgs4.out_en similarity index 100% rename from python/test-data-2.0/optionalArgs4.out_en rename to python/test-data-2.0/basics/optionalArgs4.out_en diff --git a/python/test-data-2.0/optionalArgs4.py b/python/test-data-2.0/basics/optionalArgs4.py similarity index 100% rename from python/test-data-2.0/optionalArgs4.py rename to python/test-data-2.0/basics/optionalArgs4.py diff --git a/python/test-data-2.0/optionalArgs_ok.err b/python/test-data-2.0/basics/optionalArgs_ok.err similarity index 100% rename from python/test-data-2.0/optionalArgs_ok.err rename to python/test-data-2.0/basics/optionalArgs_ok.err diff --git a/python/test-data-2.0/optionalArgs_ok.out b/python/test-data-2.0/basics/optionalArgs_ok.out similarity index 100% rename from python/test-data-2.0/optionalArgs_ok.out rename to python/test-data-2.0/basics/optionalArgs_ok.out diff --git a/python/test-data-2.0/optionalArgs_ok.py b/python/test-data-2.0/basics/optionalArgs_ok.py similarity index 100% rename from python/test-data-2.0/optionalArgs_ok.py rename to python/test-data-2.0/basics/optionalArgs_ok.py diff --git a/python/test-data-2.0/optionalAttr.err b/python/test-data-2.0/basics/optionalAttr.err similarity index 100% rename from python/test-data-2.0/optionalAttr.err rename to python/test-data-2.0/basics/optionalAttr.err diff --git a/python/test-data-2.0/optionalAttr.err_en b/python/test-data-2.0/basics/optionalAttr.err_en similarity index 100% rename from python/test-data-2.0/optionalAttr.err_en rename to python/test-data-2.0/basics/optionalAttr.err_en diff --git a/python/test-data-2.0/optionalAttr.out b/python/test-data-2.0/basics/optionalAttr.out similarity index 100% rename from python/test-data-2.0/optionalAttr.out rename to python/test-data-2.0/basics/optionalAttr.out diff --git a/python/test-data-2.0/optionalAttr.out_en b/python/test-data-2.0/basics/optionalAttr.out_en similarity index 100% rename from python/test-data-2.0/optionalAttr.out_en rename to python/test-data-2.0/basics/optionalAttr.out_en diff --git a/python/test-data-2.0/optionalAttr.py b/python/test-data-2.0/basics/optionalAttr.py similarity index 100% rename from python/test-data-2.0/optionalAttr.py rename to python/test-data-2.0/basics/optionalAttr.py diff --git a/python/test-data-2.0/optionalAttr2.err b/python/test-data-2.0/basics/optionalAttr2.err similarity index 78% rename from python/test-data-2.0/optionalAttr2.err rename to python/test-data-2.0/basics/optionalAttr2.err index c01979f2..ce675bec 100644 --- a/python/test-data-2.0/optionalAttr2.err +++ b/python/test-data-2.0/basics/optionalAttr2.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: Anzahl der Argument passt nicht -Konstruktor der Klasse `C` benötigt mindestens 2 Argumente. +Konstruktor des Records `C` benötigt mindestens 2 Argumente. Gegeben: 1 Argument ## Datei optionalAttr2.py diff --git a/python/test-data-2.0/optionalAttr2.err_en b/python/test-data-2.0/basics/optionalAttr2.err_en similarity index 79% rename from python/test-data-2.0/optionalAttr2.err_en rename to python/test-data-2.0/basics/optionalAttr2.err_en index 8e86d632..79ec47df 100644 --- a/python/test-data-2.0/optionalAttr2.err_en +++ b/python/test-data-2.0/basics/optionalAttr2.err_en @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: argument count mismatch -Constructor of class `C` takes at least 2 arguments. +Constructor of record `C` takes at least 2 arguments. Given: 1 argument ## File optionalAttr2.py diff --git a/python/test-data-2.0/optionalAttr2.out b/python/test-data-2.0/basics/optionalAttr2.out similarity index 100% rename from python/test-data-2.0/optionalAttr2.out rename to python/test-data-2.0/basics/optionalAttr2.out diff --git a/python/test-data-2.0/optionalAttr2.out_en b/python/test-data-2.0/basics/optionalAttr2.out_en similarity index 100% rename from python/test-data-2.0/optionalAttr2.out_en rename to python/test-data-2.0/basics/optionalAttr2.out_en diff --git a/python/test-data-2.0/optionalAttr2.py b/python/test-data-2.0/basics/optionalAttr2.py similarity index 100% rename from python/test-data-2.0/optionalAttr2.py rename to python/test-data-2.0/basics/optionalAttr2.py diff --git a/python/test-data-2.0/optionalAttr3.err b/python/test-data-2.0/basics/optionalAttr3.err similarity index 79% rename from python/test-data-2.0/optionalAttr3.err rename to python/test-data-2.0/basics/optionalAttr3.err index 8271acb0..9890de0a 100644 --- a/python/test-data-2.0/optionalAttr3.err +++ b/python/test-data-2.0/basics/optionalAttr3.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: Anzahl der Argument passt nicht -Konstruktor der Klasse `C` akzeptiert höchstens 3 Argumente. +Konstruktor des Records `C` akzeptiert höchstens 3 Argumente. Gegeben: 4 Argumente ## Datei optionalAttr3.py diff --git a/python/test-data-2.0/optionalAttr3.err_en b/python/test-data-2.0/basics/optionalAttr3.err_en similarity index 81% rename from python/test-data-2.0/optionalAttr3.err_en rename to python/test-data-2.0/basics/optionalAttr3.err_en index a3c6b21e..bcb14c41 100644 --- a/python/test-data-2.0/optionalAttr3.err_en +++ b/python/test-data-2.0/basics/optionalAttr3.err_en @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: argument count mismatch -Constructor of class `C` takes at most 3 arguments. +Constructor of record `C` takes at most 3 arguments. Given: 4 arguments ## File optionalAttr3.py diff --git a/python/test-data-2.0/optionalAttr3.out b/python/test-data-2.0/basics/optionalAttr3.out similarity index 100% rename from python/test-data-2.0/optionalAttr3.out rename to python/test-data-2.0/basics/optionalAttr3.out diff --git a/python/test-data-2.0/optionalAttr3.out_en b/python/test-data-2.0/basics/optionalAttr3.out_en similarity index 100% rename from python/test-data-2.0/optionalAttr3.out_en rename to python/test-data-2.0/basics/optionalAttr3.out_en diff --git a/python/test-data-2.0/optionalAttr3.py b/python/test-data-2.0/basics/optionalAttr3.py similarity index 100% rename from python/test-data-2.0/optionalAttr3.py rename to python/test-data-2.0/basics/optionalAttr3.py diff --git a/python/test-data-2.0/optionalAttr_ok.err b/python/test-data-2.0/basics/optionalAttr_ok.err similarity index 100% rename from python/test-data-2.0/optionalAttr_ok.err rename to python/test-data-2.0/basics/optionalAttr_ok.err diff --git a/python/test-data-2.0/optionalAttr_ok.out b/python/test-data-2.0/basics/optionalAttr_ok.out similarity index 100% rename from python/test-data-2.0/optionalAttr_ok.out rename to python/test-data-2.0/basics/optionalAttr_ok.out diff --git a/python/test-data-2.0/optionalAttr_ok.py b/python/test-data-2.0/basics/optionalAttr_ok.py similarity index 100% rename from python/test-data-2.0/optionalAttr_ok.py rename to python/test-data-2.0/basics/optionalAttr_ok.py diff --git a/python/test-data-2.0/partial.err b/python/test-data-2.0/basics/partial.err similarity index 100% rename from python/test-data-2.0/partial.err rename to python/test-data-2.0/basics/partial.err diff --git a/python/test-data-2.0/partial.err_en b/python/test-data-2.0/basics/partial.err_en similarity index 100% rename from python/test-data-2.0/partial.err_en rename to python/test-data-2.0/basics/partial.err_en diff --git a/python/test-data-2.0/partial.out b/python/test-data-2.0/basics/partial.out similarity index 100% rename from python/test-data-2.0/partial.out rename to python/test-data-2.0/basics/partial.out diff --git a/python/test-data-2.0/partial.out_en b/python/test-data-2.0/basics/partial.out_en similarity index 100% rename from python/test-data-2.0/partial.out_en rename to python/test-data-2.0/basics/partial.out_en diff --git a/python/test-data-2.0/partial.py b/python/test-data-2.0/basics/partial.py similarity index 100% rename from python/test-data-2.0/partial.py rename to python/test-data-2.0/basics/partial.py diff --git a/python/test-data-2.0/record.err b/python/test-data-2.0/basics/record.err similarity index 76% rename from python/test-data-2.0/record.err rename to python/test-data-2.0/basics/record.err index 0f3bb7b8..14556db8 100644 --- a/python/test-data-2.0/record.err +++ b/python/test-data-2.0/basics/record.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: "30" -Der Aufruf des Konstruktors der Klasse `Person` erwartet Wert vom Typ `int` als zweites Argument. +Der Aufruf des Konstruktors des Records `Person` erwartet Wert vom Typ `int` als zweites Argument. Aber der übergebene Wert hat Typ `str`. ## Datei record.py diff --git a/python/test-data-2.0/record.err_en b/python/test-data-2.0/basics/record.err_en similarity index 77% rename from python/test-data-2.0/record.err_en rename to python/test-data-2.0/basics/record.err_en index 7e5cc156..e0b1ad06 100644 --- a/python/test-data-2.0/record.err_en +++ b/python/test-data-2.0/basics/record.err_en @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: "30" -The call of the constructor of class `Person` expects value of type `int` as 2nd argument. +The call of the constructor of record `Person` expects value of type `int` as 2nd argument. But the value given has type `str`. ## File record.py diff --git a/python/test-data-2.0/record.out b/python/test-data-2.0/basics/record.out similarity index 100% rename from python/test-data-2.0/record.out rename to python/test-data-2.0/basics/record.out diff --git a/python/test-data-2.0/record.out_en b/python/test-data-2.0/basics/record.out_en similarity index 100% rename from python/test-data-2.0/record.out_en rename to python/test-data-2.0/basics/record.out_en diff --git a/python/test-data-2.0/record.py b/python/test-data-2.0/basics/record.py similarity index 100% rename from python/test-data-2.0/record.py rename to python/test-data-2.0/basics/record.py diff --git a/python/test-data-2.0/record_ok.err b/python/test-data-2.0/basics/record_ok.err similarity index 100% rename from python/test-data-2.0/record_ok.err rename to python/test-data-2.0/basics/record_ok.err diff --git a/python/test-data-2.0/record_ok.out b/python/test-data-2.0/basics/record_ok.out similarity index 100% rename from python/test-data-2.0/record_ok.out rename to python/test-data-2.0/basics/record_ok.out diff --git a/python/test-data-2.0/record_ok.py b/python/test-data-2.0/basics/record_ok.py similarity index 100% rename from python/test-data-2.0/record_ok.py rename to python/test-data-2.0/basics/record_ok.py diff --git a/python/test-data-2.0/basics/stack.err b/python/test-data-2.0/basics/stack.err new file mode 100644 index 00000000..af02db0c --- /dev/null +++ b/python/test-data-2.0/basics/stack.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "", line 7, in + factorial(5) + File "", line 5, in factorial + return factorial(n - 1) * n + File "", line 5, in factorial + return factorial(n - 1) * n + File "", line 5, in factorial + return factorial(n - 1) * n + [Previous line repeated 2 more times] + File "", line 3, in factorial + raise ValueError('kein Bock') +ValueError: kein Bock diff --git a/python/test-data-2.0/staticmethod.out b/python/test-data-2.0/basics/stack.out similarity index 100% rename from python/test-data-2.0/staticmethod.out rename to python/test-data-2.0/basics/stack.out diff --git a/python/test-data-2.0/basics/stack.py b/python/test-data-2.0/basics/stack.py new file mode 100644 index 00000000..96c2f3bf --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/staticmethod.err b/python/test-data-2.0/basics/staticmethod.err similarity index 100% rename from python/test-data-2.0/staticmethod.err rename to python/test-data-2.0/basics/staticmethod.err diff --git a/python/test-data-2.0/staticmethod.err_en b/python/test-data-2.0/basics/staticmethod.err_en similarity index 100% rename from python/test-data-2.0/staticmethod.err_en rename to python/test-data-2.0/basics/staticmethod.err_en diff --git a/python/test-data-2.0/staticmethod.out_en b/python/test-data-2.0/basics/staticmethod.out similarity index 100% rename from python/test-data-2.0/staticmethod.out_en rename to python/test-data-2.0/basics/staticmethod.out diff --git a/python/test-data-2.0/staticmethod_ok.err b/python/test-data-2.0/basics/staticmethod.out_en similarity index 100% rename from python/test-data-2.0/staticmethod_ok.err rename to python/test-data-2.0/basics/staticmethod.out_en diff --git a/python/test-data-2.0/staticmethod.py b/python/test-data-2.0/basics/staticmethod.py similarity index 100% rename from python/test-data-2.0/staticmethod.py rename to python/test-data-2.0/basics/staticmethod.py diff --git a/python/test-data-2.0/testCallable.out b/python/test-data-2.0/basics/staticmethod_ok.err similarity index 100% rename from python/test-data-2.0/testCallable.out rename to python/test-data-2.0/basics/staticmethod_ok.err diff --git a/python/test-data-2.0/staticmethod_ok.out b/python/test-data-2.0/basics/staticmethod_ok.out similarity index 100% rename from python/test-data-2.0/staticmethod_ok.out rename to python/test-data-2.0/basics/staticmethod_ok.out diff --git a/python/test-data-2.0/staticmethod_ok.py b/python/test-data-2.0/basics/staticmethod_ok.py similarity index 100% rename from python/test-data-2.0/staticmethod_ok.py rename to python/test-data-2.0/basics/staticmethod_ok.py diff --git a/python/test-data-2.0/testCallable.err b/python/test-data-2.0/basics/testCallable.err similarity index 100% rename from python/test-data-2.0/testCallable.err rename to python/test-data-2.0/basics/testCallable.err diff --git a/python/test-data-2.0/tooFewArgs.out b/python/test-data-2.0/basics/testCallable.out similarity index 100% rename from python/test-data-2.0/tooFewArgs.out rename to python/test-data-2.0/basics/testCallable.out diff --git a/python/test-data-2.0/testCallable.py b/python/test-data-2.0/basics/testCallable.py similarity index 100% rename from python/test-data-2.0/testCallable.py rename to python/test-data-2.0/basics/testCallable.py diff --git a/python/test-data-2.0/tooFewArgs.err b/python/test-data-2.0/basics/tooFewArgs.err similarity index 100% rename from python/test-data-2.0/tooFewArgs.err rename to python/test-data-2.0/basics/tooFewArgs.err diff --git a/python/test-data-2.0/tooFewAttrs.out b/python/test-data-2.0/basics/tooFewArgs.out similarity index 100% rename from python/test-data-2.0/tooFewAttrs.out rename to python/test-data-2.0/basics/tooFewArgs.out diff --git a/python/test-data-2.0/tooFewArgs.py b/python/test-data-2.0/basics/tooFewArgs.py similarity index 100% rename from python/test-data-2.0/tooFewArgs.py rename to python/test-data-2.0/basics/tooFewArgs.py diff --git a/python/test-data-2.0/tooFewAttrs.err b/python/test-data-2.0/basics/tooFewAttrs.err similarity index 81% rename from python/test-data-2.0/tooFewAttrs.err rename to python/test-data-2.0/basics/tooFewAttrs.err index 56d360c5..004ca134 100644 --- a/python/test-data-2.0/tooFewAttrs.err +++ b/python/test-data-2.0/basics/tooFewAttrs.err @@ -4,7 +4,7 @@ Traceback (most recent call last): WyppTypeError: Anzahl der Argument passt nicht -Konstruktor der Klasse `C` benötigt 2 Argumente. +Konstruktor des Records `C` benötigt 2 Argumente. Gegeben: 1 Argument ## Datei tooFewAttrs.py diff --git a/python/test-data-2.0/tooManyArgs.out b/python/test-data-2.0/basics/tooFewAttrs.out similarity index 100% rename from python/test-data-2.0/tooManyArgs.out rename to python/test-data-2.0/basics/tooFewAttrs.out diff --git a/python/test-data-2.0/tooFewAttrs.py b/python/test-data-2.0/basics/tooFewAttrs.py similarity index 100% rename from python/test-data-2.0/tooFewAttrs.py rename to python/test-data-2.0/basics/tooFewAttrs.py diff --git a/python/test-data-2.0/tooManyArgs.err b/python/test-data-2.0/basics/tooManyArgs.err similarity index 100% rename from python/test-data-2.0/tooManyArgs.err rename to python/test-data-2.0/basics/tooManyArgs.err diff --git a/python/test-data-2.0/tooManyArgs.err_en b/python/test-data-2.0/basics/tooManyArgs.err_en similarity index 100% rename from python/test-data-2.0/tooManyArgs.err_en rename to python/test-data-2.0/basics/tooManyArgs.err_en diff --git a/python/test-data-2.0/tooManyArgs.out_en b/python/test-data-2.0/basics/tooManyArgs.out similarity index 100% rename from python/test-data-2.0/tooManyArgs.out_en rename to python/test-data-2.0/basics/tooManyArgs.out diff --git a/python/test-data-2.0/tooManyAttrs.out b/python/test-data-2.0/basics/tooManyArgs.out_en similarity index 100% rename from python/test-data-2.0/tooManyAttrs.out rename to python/test-data-2.0/basics/tooManyArgs.out_en diff --git a/python/test-data-2.0/tooManyArgs.py b/python/test-data-2.0/basics/tooManyArgs.py similarity index 100% rename from python/test-data-2.0/tooManyArgs.py rename to python/test-data-2.0/basics/tooManyArgs.py diff --git a/python/test-data-2.0/tooManyAttrs.err b/python/test-data-2.0/basics/tooManyAttrs.err similarity index 100% rename from python/test-data-2.0/tooManyAttrs.err rename to python/test-data-2.0/basics/tooManyAttrs.err diff --git a/python/test-data-2.0/tooManyAttrs.err_en b/python/test-data-2.0/basics/tooManyAttrs.err_en similarity index 100% rename from python/test-data-2.0/tooManyAttrs.err_en rename to python/test-data-2.0/basics/tooManyAttrs.err_en diff --git a/python/test-data-2.0/tooManyAttrs.out_en b/python/test-data-2.0/basics/tooManyAttrs.out similarity index 100% rename from python/test-data-2.0/tooManyAttrs.out_en rename to python/test-data-2.0/basics/tooManyAttrs.out diff --git a/python/test-data/admin.err b/python/test-data-2.0/basics/tooManyAttrs.out_en similarity index 100% rename from python/test-data/admin.err rename to python/test-data-2.0/basics/tooManyAttrs.out_en diff --git a/python/test-data-2.0/tooManyAttrs.py b/python/test-data-2.0/basics/tooManyAttrs.py similarity index 100% rename from python/test-data-2.0/tooManyAttrs.py rename to python/test-data-2.0/basics/tooManyAttrs.py diff --git a/python/test-data/printModuleName.err b/python/test-data-2.0/extras/admin_ok.err similarity index 100% rename from python/test-data/printModuleName.err rename to python/test-data-2.0/extras/admin_ok.err diff --git a/python/test-data/admin.out b/python/test-data-2.0/extras/admin_ok.out similarity index 100% rename from python/test-data/admin.out rename to python/test-data-2.0/extras/admin_ok.out diff --git a/python/test-data/admin.py b/python/test-data-2.0/extras/admin_ok.py similarity index 100% rename from python/test-data/admin.py rename to python/test-data-2.0/extras/admin_ok.py diff --git a/python/test-data/printModuleNameImport.err b/python/test-data-2.0/extras/printModuleNameImport_ok.err similarity index 100% rename from python/test-data/printModuleNameImport.err rename to python/test-data-2.0/extras/printModuleNameImport_ok.err diff --git a/python/test-data/printModuleNameImport.out b/python/test-data-2.0/extras/printModuleNameImport_ok.out similarity index 55% rename from python/test-data/printModuleNameImport.out rename to python/test-data-2.0/extras/printModuleNameImport_ok.out index c23481b5..fbf92154 100644 --- a/python/test-data/printModuleNameImport.out +++ b/python/test-data-2.0/extras/printModuleNameImport_ok.out @@ -1,4 +1,4 @@ -printModuleName +printModuleName_ok True __wypp__ True diff --git a/python/test-data-2.0/extras/printModuleNameImport_ok.py b/python/test-data-2.0/extras/printModuleNameImport_ok.py new file mode 100644 index 00000000..aeff565d --- /dev/null +++ b/python/test-data-2.0/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/testABC.err b/python/test-data-2.0/extras/printModuleName_ok.err similarity index 100% rename from python/test-data/testABC.err rename to python/test-data-2.0/extras/printModuleName_ok.err diff --git a/python/test-data/printModuleName.out b/python/test-data-2.0/extras/printModuleName_ok.out similarity index 100% rename from python/test-data/printModuleName.out rename to python/test-data-2.0/extras/printModuleName_ok.out diff --git a/python/test-data/printModuleName.py b/python/test-data-2.0/extras/printModuleName_ok.py similarity index 83% rename from python/test-data/printModuleName.py rename to python/test-data-2.0/extras/printModuleName_ok.py index 82c7cf94..cb049d40 100644 --- a/python/test-data/printModuleName.py +++ b/python/test-data-2.0/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/testBugSliceIndices.err b/python/test-data-2.0/extras/testABC_ok.err similarity index 100% rename from python/test-data/testBugSliceIndices.err rename to python/test-data-2.0/extras/testABC_ok.err diff --git a/python/test-data/testABC.out b/python/test-data-2.0/extras/testABC_ok.out similarity index 100% rename from python/test-data/testABC.out rename to python/test-data-2.0/extras/testABC_ok.out diff --git a/python/test-data/testABC.py b/python/test-data-2.0/extras/testABC_ok.py similarity index 100% rename from python/test-data/testABC.py rename to python/test-data-2.0/extras/testABC_ok.py diff --git a/python/test-data/testCheckFail.err b/python/test-data-2.0/extras/testBugSliceIndices_ok.err similarity index 100% rename from python/test-data/testCheckFail.err rename to python/test-data-2.0/extras/testBugSliceIndices_ok.err diff --git a/python/test-data/testBugSliceIndices.out b/python/test-data-2.0/extras/testBugSliceIndices_ok.out similarity index 100% rename from python/test-data/testBugSliceIndices.out rename to python/test-data-2.0/extras/testBugSliceIndices_ok.out diff --git a/python/test-data/testBugSliceIndices.py b/python/test-data-2.0/extras/testBugSliceIndices_ok.py similarity index 100% rename from python/test-data/testBugSliceIndices.py rename to python/test-data-2.0/extras/testBugSliceIndices_ok.py diff --git a/python/test-data/testClassHierarchy.err b/python/test-data-2.0/extras/testCheckFail_ok.err similarity index 100% rename from python/test-data/testClassHierarchy.err rename to python/test-data-2.0/extras/testCheckFail_ok.err diff --git a/python/test-data/testCheckFail.out b/python/test-data-2.0/extras/testCheckFail_ok.out similarity index 100% rename from python/test-data/testCheckFail.out rename to python/test-data-2.0/extras/testCheckFail_ok.out diff --git a/python/test-data/testCheckFail.py b/python/test-data-2.0/extras/testCheckFail_ok.py similarity index 57% rename from python/test-data/testCheckFail.py rename to python/test-data-2.0/extras/testCheckFail_ok.py index 1c9981ca..1ea3c05c 100644 --- a/python/test-data/testCheckFail.py +++ b/python/test-data-2.0/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/testClassRecursion.err b/python/test-data-2.0/extras/testClassHierarchy_ok.err similarity index 100% rename from python/test-data/testClassRecursion.err rename to python/test-data-2.0/extras/testClassHierarchy_ok.err diff --git a/python/test-data/testClassHierarchy.out b/python/test-data-2.0/extras/testClassHierarchy_ok.out similarity index 100% rename from python/test-data/testClassHierarchy.out rename to python/test-data-2.0/extras/testClassHierarchy_ok.out diff --git a/python/test-data/testClassHierarchy.py b/python/test-data-2.0/extras/testClassHierarchy_ok.py similarity index 100% rename from python/test-data/testClassHierarchy.py rename to python/test-data-2.0/extras/testClassHierarchy_ok.py diff --git a/python/test-data/testComplex.err b/python/test-data-2.0/extras/testClassRecursion_ok.err similarity index 100% rename from python/test-data/testComplex.err rename to python/test-data-2.0/extras/testClassRecursion_ok.err diff --git a/python/test-data/testClassRecursion.out b/python/test-data-2.0/extras/testClassRecursion_ok.out similarity index 100% rename from python/test-data/testClassRecursion.out rename to python/test-data-2.0/extras/testClassRecursion_ok.out diff --git a/python/test-data/testClassRecursion.py b/python/test-data-2.0/extras/testClassRecursion_ok.py similarity index 100% rename from python/test-data/testClassRecursion.py rename to python/test-data-2.0/extras/testClassRecursion_ok.py diff --git a/python/test-data/testComplex.out b/python/test-data-2.0/extras/testComplex_ok.err similarity index 100% rename from python/test-data/testComplex.out rename to python/test-data-2.0/extras/testComplex_ok.err diff --git a/python/test-data/testConcat.err b/python/test-data-2.0/extras/testComplex_ok.out similarity index 100% rename from python/test-data/testConcat.err rename to python/test-data-2.0/extras/testComplex_ok.out diff --git a/python/test-data/testComplex.py b/python/test-data-2.0/extras/testComplex_ok.py similarity index 100% rename from python/test-data/testComplex.py rename to python/test-data-2.0/extras/testComplex_ok.py diff --git a/python/test-data/testCopy.err b/python/test-data-2.0/extras/testConcat_ok.err similarity index 100% rename from python/test-data/testCopy.err rename to python/test-data-2.0/extras/testConcat_ok.err diff --git a/python/test-data/testConcat.out b/python/test-data-2.0/extras/testConcat_ok.out similarity index 100% rename from python/test-data/testConcat.out rename to python/test-data-2.0/extras/testConcat_ok.out diff --git a/python/test-data/testConcat.py b/python/test-data-2.0/extras/testConcat_ok.py similarity index 100% rename from python/test-data/testConcat.py rename to python/test-data-2.0/extras/testConcat_ok.py diff --git a/python/test-data/testDict.err b/python/test-data-2.0/extras/testCopy_ok.err similarity index 100% rename from python/test-data/testDict.err rename to python/test-data-2.0/extras/testCopy_ok.err diff --git a/python/test-data/testCopy.out b/python/test-data-2.0/extras/testCopy_ok.out similarity index 100% rename from python/test-data/testCopy.out rename to python/test-data-2.0/extras/testCopy_ok.out diff --git a/python/test-data/testCopy.py b/python/test-data-2.0/extras/testCopy_ok.py similarity index 100% rename from python/test-data/testCopy.py rename to python/test-data-2.0/extras/testCopy_ok.py diff --git a/python/test-data/testDoubleWrappingDicts.err b/python/test-data-2.0/extras/testDict_ok.err similarity index 100% rename from python/test-data/testDoubleWrappingDicts.err rename to python/test-data-2.0/extras/testDict_ok.err diff --git a/python/test-data/testDict.out b/python/test-data-2.0/extras/testDict_ok.out similarity index 100% rename from python/test-data/testDict.out rename to python/test-data-2.0/extras/testDict_ok.out diff --git a/python/test-data/testDict.py b/python/test-data-2.0/extras/testDict_ok.py similarity index 100% rename from python/test-data/testDict.py rename to python/test-data-2.0/extras/testDict_ok.py diff --git a/python/test-data/testForwardRef.err b/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.err similarity index 100% rename from python/test-data/testForwardRef.err rename to python/test-data-2.0/extras/testDoubleWrappingDicts_ok.err diff --git a/python/test-data/testDoubleWrappingDicts.out b/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.out similarity index 100% rename from python/test-data/testDoubleWrappingDicts.out rename to python/test-data-2.0/extras/testDoubleWrappingDicts_ok.out diff --git a/python/test-data/testDoubleWrappingDicts.py b/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.py similarity index 100% rename from python/test-data/testDoubleWrappingDicts.py rename to python/test-data-2.0/extras/testDoubleWrappingDicts_ok.py diff --git a/python/test-data/testForwardRef.out b/python/test-data-2.0/extras/testForwardRef1_ok.err similarity index 100% rename from python/test-data/testForwardRef.out rename to python/test-data-2.0/extras/testForwardRef1_ok.err diff --git a/python/test-data/testForwardRef1.out b/python/test-data-2.0/extras/testForwardRef1_ok.out similarity index 100% rename from python/test-data/testForwardRef1.out rename to python/test-data-2.0/extras/testForwardRef1_ok.out diff --git a/python/test-data/testForwardRef1.py b/python/test-data-2.0/extras/testForwardRef1_ok.py similarity index 100% rename from python/test-data/testForwardRef1.py rename to python/test-data-2.0/extras/testForwardRef1_ok.py diff --git a/python/test-data/testForwardRef1.err b/python/test-data-2.0/extras/testForwardRef3_ok.err similarity index 100% rename from python/test-data/testForwardRef1.err rename to python/test-data-2.0/extras/testForwardRef3_ok.err diff --git a/python/test-data/testForwardRef3.out b/python/test-data-2.0/extras/testForwardRef3_ok.out similarity index 100% rename from python/test-data/testForwardRef3.out rename to python/test-data-2.0/extras/testForwardRef3_ok.out diff --git a/python/test-data/testForwardRef3.py b/python/test-data-2.0/extras/testForwardRef3_ok.py similarity index 100% rename from python/test-data/testForwardRef3.py rename to python/test-data-2.0/extras/testForwardRef3_ok.py diff --git a/python/test-data/testForwardRef3.err b/python/test-data-2.0/extras/testForwardRef6_ok.err similarity index 100% rename from python/test-data/testForwardRef3.err rename to python/test-data-2.0/extras/testForwardRef6_ok.err diff --git a/python/test-data/testForwardRef6.out b/python/test-data-2.0/extras/testForwardRef6_ok.out similarity index 100% rename from python/test-data/testForwardRef6.out rename to python/test-data-2.0/extras/testForwardRef6_ok.out diff --git a/python/test-data/testForwardRef6.py b/python/test-data-2.0/extras/testForwardRef6_ok.py similarity index 100% rename from python/test-data/testForwardRef6.py rename to python/test-data-2.0/extras/testForwardRef6_ok.py diff --git a/python/test-data/testForwardRef6.err b/python/test-data-2.0/extras/testForwardRef_ok.err similarity index 100% rename from python/test-data/testForwardRef6.err rename to python/test-data-2.0/extras/testForwardRef_ok.err diff --git a/python/test-data/testForwardTypeInRecord.err b/python/test-data-2.0/extras/testForwardRef_ok.out similarity index 100% rename from python/test-data/testForwardTypeInRecord.err rename to python/test-data-2.0/extras/testForwardRef_ok.out diff --git a/python/test-data/testForwardRef.py b/python/test-data-2.0/extras/testForwardRef_ok.py similarity index 100% rename from python/test-data/testForwardRef.py rename to python/test-data-2.0/extras/testForwardRef_ok.py diff --git a/python/test-data/testForwardTypeInRecord2.err b/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.err similarity index 100% rename from python/test-data/testForwardTypeInRecord2.err rename to python/test-data-2.0/extras/testForwardTypeInRecord2_ok.err diff --git a/python/test-data/testForwardTypeInRecord.out b/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.out similarity index 100% rename from python/test-data/testForwardTypeInRecord.out rename to python/test-data-2.0/extras/testForwardTypeInRecord2_ok.out diff --git a/python/test-data/testForwardTypeInRecord2.py b/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.py similarity index 100% rename from python/test-data/testForwardTypeInRecord2.py rename to python/test-data-2.0/extras/testForwardTypeInRecord2_ok.py diff --git a/python/test-data/testHof.err b/python/test-data-2.0/extras/testForwardTypeInRecord_ok.err similarity index 100% rename from python/test-data/testHof.err rename to python/test-data-2.0/extras/testForwardTypeInRecord_ok.err diff --git a/python/test-data/testForwardTypeInRecord2.out b/python/test-data-2.0/extras/testForwardTypeInRecord_ok.out similarity index 100% rename from python/test-data/testForwardTypeInRecord2.out rename to python/test-data-2.0/extras/testForwardTypeInRecord_ok.out diff --git a/python/test-data/testForwardTypeInRecord.py b/python/test-data-2.0/extras/testForwardTypeInRecord_ok.py similarity index 100% rename from python/test-data/testForwardTypeInRecord.py rename to python/test-data-2.0/extras/testForwardTypeInRecord_ok.py diff --git a/python/test-data/testIndexSeq.err b/python/test-data-2.0/extras/testHof_ok.err similarity index 100% rename from python/test-data/testIndexSeq.err rename to python/test-data-2.0/extras/testHof_ok.err diff --git a/python/test-data/testHof.out b/python/test-data-2.0/extras/testHof_ok.out similarity index 100% rename from python/test-data/testHof.out rename to python/test-data-2.0/extras/testHof_ok.out diff --git a/python/test-data/testHof.py b/python/test-data-2.0/extras/testHof_ok.py similarity index 100% rename from python/test-data/testHof.py rename to python/test-data-2.0/extras/testHof_ok.py diff --git a/python/test-data/testIterable1.err b/python/test-data-2.0/extras/testIndexSeq_ok.err similarity index 100% rename from python/test-data/testIterable1.err rename to python/test-data-2.0/extras/testIndexSeq_ok.err diff --git a/python/test-data/testIndexSeq.out b/python/test-data-2.0/extras/testIndexSeq_ok.out similarity index 100% rename from python/test-data/testIndexSeq.out rename to python/test-data-2.0/extras/testIndexSeq_ok.out diff --git a/python/test-data/testIndexSeq.py b/python/test-data-2.0/extras/testIndexSeq_ok.py similarity index 100% rename from python/test-data/testIndexSeq.py rename to python/test-data-2.0/extras/testIndexSeq_ok.py diff --git a/python/test-data/testIterable2.err b/python/test-data-2.0/extras/testIterable1_ok.err similarity index 100% rename from python/test-data/testIterable2.err rename to python/test-data-2.0/extras/testIterable1_ok.err diff --git a/python/test-data/testIterable1.out b/python/test-data-2.0/extras/testIterable1_ok.out similarity index 100% rename from python/test-data/testIterable1.out rename to python/test-data-2.0/extras/testIterable1_ok.out diff --git a/python/test-data/testIterable1.py b/python/test-data-2.0/extras/testIterable1_ok.py similarity index 100% rename from python/test-data/testIterable1.py rename to python/test-data-2.0/extras/testIterable1_ok.py diff --git a/python/test-data/testIterable3.err b/python/test-data-2.0/extras/testIterable2_ok.err similarity index 100% rename from python/test-data/testIterable3.err rename to python/test-data-2.0/extras/testIterable2_ok.err diff --git a/python/test-data/testIterable2.out b/python/test-data-2.0/extras/testIterable2_ok.out similarity index 100% rename from python/test-data/testIterable2.out rename to python/test-data-2.0/extras/testIterable2_ok.out diff --git a/python/test-data/testIterable2.py b/python/test-data-2.0/extras/testIterable2_ok.py similarity index 100% rename from python/test-data/testIterable2.py rename to python/test-data-2.0/extras/testIterable2_ok.py diff --git a/python/test-data/testIterable4.err b/python/test-data-2.0/extras/testIterable3_ok.err similarity index 100% rename from python/test-data/testIterable4.err rename to python/test-data-2.0/extras/testIterable3_ok.err diff --git a/python/test-data/testIterable3.out b/python/test-data-2.0/extras/testIterable3_ok.out similarity index 100% rename from python/test-data/testIterable3.out rename to python/test-data-2.0/extras/testIterable3_ok.out diff --git a/python/test-data/testIterable3.py b/python/test-data-2.0/extras/testIterable3_ok.py similarity index 100% rename from python/test-data/testIterable3.py rename to python/test-data-2.0/extras/testIterable3_ok.py diff --git a/python/test-data/testIterable5.err b/python/test-data-2.0/extras/testIterable4_ok.err similarity index 100% rename from python/test-data/testIterable5.err rename to python/test-data-2.0/extras/testIterable4_ok.err diff --git a/python/test-data/testIterable4.out b/python/test-data-2.0/extras/testIterable4_ok.out similarity index 100% rename from python/test-data/testIterable4.out rename to python/test-data-2.0/extras/testIterable4_ok.out diff --git a/python/test-data/testIterable4.py b/python/test-data-2.0/extras/testIterable4_ok.py similarity index 100% rename from python/test-data/testIterable4.py rename to python/test-data-2.0/extras/testIterable4_ok.py diff --git a/python/test-data/testIterable6.err b/python/test-data-2.0/extras/testIterable5_ok.err similarity index 100% rename from python/test-data/testIterable6.err rename to python/test-data-2.0/extras/testIterable5_ok.err diff --git a/python/test-data/testIterable5.out b/python/test-data-2.0/extras/testIterable5_ok.out similarity index 100% rename from python/test-data/testIterable5.out rename to python/test-data-2.0/extras/testIterable5_ok.out diff --git a/python/test-data/testIterable5.py b/python/test-data-2.0/extras/testIterable5_ok.py similarity index 100% rename from python/test-data/testIterable5.py rename to python/test-data-2.0/extras/testIterable5_ok.py diff --git a/python/test-data/testIterator.err b/python/test-data-2.0/extras/testIterable6_ok.err similarity index 100% rename from python/test-data/testIterator.err rename to python/test-data-2.0/extras/testIterable6_ok.err diff --git a/python/test-data/testIterable6.out b/python/test-data-2.0/extras/testIterable6_ok.out similarity index 100% rename from python/test-data/testIterable6.out rename to python/test-data-2.0/extras/testIterable6_ok.out diff --git a/python/test-data/testIterable6.py b/python/test-data-2.0/extras/testIterable6_ok.py similarity index 100% rename from python/test-data/testIterable6.py rename to python/test-data-2.0/extras/testIterable6_ok.py diff --git a/python/test-data/testIterator2.err b/python/test-data-2.0/extras/testIterator2_ok.err similarity index 100% rename from python/test-data/testIterator2.err rename to python/test-data-2.0/extras/testIterator2_ok.err diff --git a/python/test-data/testIterator.out b/python/test-data-2.0/extras/testIterator2_ok.out similarity index 100% rename from python/test-data/testIterator.out rename to python/test-data-2.0/extras/testIterator2_ok.out diff --git a/python/test-data/testIterator2.py b/python/test-data-2.0/extras/testIterator2_ok.py similarity index 100% rename from python/test-data/testIterator2.py rename to python/test-data-2.0/extras/testIterator2_ok.py diff --git a/python/test-data/testIterator3.err b/python/test-data-2.0/extras/testIterator3_ok.err similarity index 100% rename from python/test-data/testIterator3.err rename to python/test-data-2.0/extras/testIterator3_ok.err diff --git a/python/test-data/testIterator3.out b/python/test-data-2.0/extras/testIterator3_ok.out similarity index 100% rename from python/test-data/testIterator3.out rename to python/test-data-2.0/extras/testIterator3_ok.out diff --git a/python/test-data/testIterator3.py b/python/test-data-2.0/extras/testIterator3_ok.py similarity index 100% rename from python/test-data/testIterator3.py rename to python/test-data-2.0/extras/testIterator3_ok.py diff --git a/python/test-data/testIterator4.err b/python/test-data-2.0/extras/testIterator4_ok.err similarity index 100% rename from python/test-data/testIterator4.err rename to python/test-data-2.0/extras/testIterator4_ok.err diff --git a/python/test-data/testIterator4.out b/python/test-data-2.0/extras/testIterator4_ok.out similarity index 100% rename from python/test-data/testIterator4.out rename to python/test-data-2.0/extras/testIterator4_ok.out diff --git a/python/test-data/testIterator4.py b/python/test-data-2.0/extras/testIterator4_ok.py similarity index 100% rename from python/test-data/testIterator4.py rename to python/test-data-2.0/extras/testIterator4_ok.py diff --git a/python/test-data/testLiteralInstanceOf.err b/python/test-data-2.0/extras/testIterator_ok.err similarity index 100% rename from python/test-data/testLiteralInstanceOf.err rename to python/test-data-2.0/extras/testIterator_ok.err diff --git a/python/test-data/testIterator2.out b/python/test-data-2.0/extras/testIterator_ok.out similarity index 100% rename from python/test-data/testIterator2.out rename to python/test-data-2.0/extras/testIterator_ok.out diff --git a/python/test-data/testIterator.py b/python/test-data-2.0/extras/testIterator_ok.py similarity index 100% rename from python/test-data/testIterator.py rename to python/test-data-2.0/extras/testIterator_ok.py diff --git a/python/test-data/testNameErrorBug.err b/python/test-data-2.0/extras/testLiteralInstanceOf_ok.err similarity index 100% rename from python/test-data/testNameErrorBug.err rename to python/test-data-2.0/extras/testLiteralInstanceOf_ok.err diff --git a/python/test-data/testLiteralInstanceOf.out b/python/test-data-2.0/extras/testLiteralInstanceOf_ok.out similarity index 100% rename from python/test-data/testLiteralInstanceOf.out rename to python/test-data-2.0/extras/testLiteralInstanceOf_ok.out diff --git a/python/test-data/testLiteralInstanceOf.py b/python/test-data-2.0/extras/testLiteralInstanceOf_ok.py similarity index 100% rename from python/test-data/testLiteralInstanceOf.py rename to python/test-data-2.0/extras/testLiteralInstanceOf_ok.py diff --git a/python/test-data/testNameErrorBug.out b/python/test-data-2.0/extras/testNameErrorBug_ok.err similarity index 100% rename from python/test-data/testNameErrorBug.out rename to python/test-data-2.0/extras/testNameErrorBug_ok.err diff --git a/python/test-data/testTypeKeyword.err b/python/test-data-2.0/extras/testNameErrorBug_ok.out similarity index 100% rename from python/test-data/testTypeKeyword.err rename to python/test-data-2.0/extras/testNameErrorBug_ok.out diff --git a/python/test-data/testNameErrorBug.py b/python/test-data-2.0/extras/testNameErrorBug_ok.py similarity index 100% rename from python/test-data/testNameErrorBug.py rename to python/test-data-2.0/extras/testNameErrorBug_ok.py diff --git a/python/test-data/testTypes2.err-notypes b/python/test-data-2.0/extras/testTypeKeyword_ok.err similarity index 100% rename from python/test-data/testTypes2.err-notypes rename to python/test-data-2.0/extras/testTypeKeyword_ok.err diff --git a/python/test-data/testTypeKeyword.out b/python/test-data-2.0/extras/testTypeKeyword_ok.out similarity index 100% rename from python/test-data/testTypeKeyword.out rename to python/test-data-2.0/extras/testTypeKeyword_ok.out diff --git a/python/test-data/testTypeKeyword.py b/python/test-data-2.0/extras/testTypeKeyword_ok.py similarity index 82% rename from python/test-data/testTypeKeyword.py rename to python/test-data-2.0/extras/testTypeKeyword_ok.py index a9125795..d98ceb0b 100644 --- a/python/test-data/testTypeKeyword.py +++ b/python/test-data-2.0/extras/testTypeKeyword_ok.py @@ -1,3 +1,4 @@ +# WYPP_TEST_CONFIG: {"typecheck": "both"} from __future__ import annotations from wypp import * diff --git a/python/test-data-2.0/extras/testTypes2.err b/python/test-data-2.0/extras/testTypes2.err new file mode 100644 index 00000000..6f3b6857 --- /dev/null +++ b/python/test-data-2.0/extras/testTypes2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "", 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 testTypes2.py +## Fehlerhafter Aufruf in Zeile 4: + +inc("1") + +## Typ deklariert in Zeile 1: + +def inc(x: int) -> int: diff --git a/python/test-data/testTypes2.out b/python/test-data-2.0/extras/testTypes2.err-notypes similarity index 100% rename from python/test-data/testTypes2.out rename to python/test-data-2.0/extras/testTypes2.err-notypes diff --git a/python/test-data/testTypesCollections3.out b/python/test-data-2.0/extras/testTypes2.out similarity index 100% rename from python/test-data/testTypesCollections3.out rename to python/test-data-2.0/extras/testTypes2.out diff --git a/python/test-data/testTypes2.py b/python/test-data-2.0/extras/testTypes2.py similarity index 100% rename from python/test-data/testTypes2.py rename to python/test-data-2.0/extras/testTypes2.py diff --git a/python/test-data-2.0/extras/testTypes2_ok.py b/python/test-data-2.0/extras/testTypes2_ok.py new file mode 100644 index 00000000..c99dca5c --- /dev/null +++ b/python/test-data-2.0/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/test-data/testTypesCollections4.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.err similarity index 100% rename from python/test-data/testTypesCollections4.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.err diff --git a/python/test-data/testTypesHigherOrderFuns2.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.out similarity index 100% rename from python/test-data/testTypesHigherOrderFuns2.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.out diff --git a/python/test-data/testTypesHigherOrderFuns2.py b/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.py similarity index 100% rename from python/test-data/testTypesHigherOrderFuns2.py rename to python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.py diff --git a/python/test-data/testTypesHigherOrderFuns2.err b/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.err similarity index 100% rename from python/test-data/testTypesHigherOrderFuns2.err rename to python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.err diff --git a/python/test-data/testTypesHigherOrderFuns4.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.out similarity index 100% rename from python/test-data/testTypesHigherOrderFuns4.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.out diff --git a/python/test-data/testTypesHigherOrderFuns4.py b/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.py similarity index 100% rename from python/test-data/testTypesHigherOrderFuns4.py rename to python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.py diff --git a/python/test-data/testTypesHigherOrderFuns4.err b/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.err similarity index 100% rename from python/test-data/testTypesHigherOrderFuns4.err rename to python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.err diff --git a/python/test-data/testTypesHigherOrderFuns5.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.out similarity index 100% rename from python/test-data/testTypesHigherOrderFuns5.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.out diff --git a/python/test-data/testTypesHigherOrderFuns5.py b/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.py similarity index 100% rename from python/test-data/testTypesHigherOrderFuns5.py rename to python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.py diff --git a/python/test-data/testTypesHigherOrderFuns5.err b/python/test-data-2.0/extras/testTypesProtos5_ok.err similarity index 100% rename from python/test-data/testTypesHigherOrderFuns5.err rename to python/test-data-2.0/extras/testTypesProtos5_ok.err diff --git a/python/test-data/testTypesProtos5.out b/python/test-data-2.0/extras/testTypesProtos5_ok.out similarity index 100% rename from python/test-data/testTypesProtos5.out rename to python/test-data-2.0/extras/testTypesProtos5_ok.out diff --git a/python/test-data/testTypesProtos5.py b/python/test-data-2.0/extras/testTypesProtos5_ok.py similarity index 100% rename from python/test-data/testTypesProtos5.py rename to python/test-data-2.0/extras/testTypesProtos5_ok.py diff --git a/python/test-data/testTypesProtos5.err b/python/test-data-2.0/extras/testTypesWrapperEq_ok.err similarity index 100% rename from python/test-data/testTypesProtos5.err rename to python/test-data-2.0/extras/testTypesWrapperEq_ok.err diff --git a/python/test-data/testTypesWrapperEq.out b/python/test-data-2.0/extras/testTypesWrapperEq_ok.out similarity index 100% rename from python/test-data/testTypesWrapperEq.out rename to python/test-data-2.0/extras/testTypesWrapperEq_ok.out diff --git a/python/test-data/testTypesWrapperEq.py b/python/test-data-2.0/extras/testTypesWrapperEq_ok.py similarity index 100% rename from python/test-data/testTypesWrapperEq.py rename to python/test-data-2.0/extras/testTypesWrapperEq_ok.py diff --git a/python/test-data/testTypesWrapperEq.err b/python/test-data-2.0/extras/testUnion2_ok.err similarity index 100% rename from python/test-data/testTypesWrapperEq.err rename to python/test-data-2.0/extras/testUnion2_ok.err diff --git a/python/test-data/testUnion2.out b/python/test-data-2.0/extras/testUnion2_ok.out similarity index 100% rename from python/test-data/testUnion2.out rename to python/test-data-2.0/extras/testUnion2_ok.out diff --git a/python/test-data/testUnion2.py b/python/test-data-2.0/extras/testUnion2_ok.py similarity index 100% rename from python/test-data/testUnion2.py rename to python/test-data-2.0/extras/testUnion2_ok.py diff --git a/python/test-data/testUnion.err b/python/test-data-2.0/extras/testUnion3_ok.err similarity index 100% rename from python/test-data/testUnion.err rename to python/test-data-2.0/extras/testUnion3_ok.err diff --git a/python/test-data/testUnion3.out b/python/test-data-2.0/extras/testUnion3_ok.out similarity index 100% rename from python/test-data/testUnion3.out rename to python/test-data-2.0/extras/testUnion3_ok.out diff --git a/python/test-data/testUnion3.py b/python/test-data-2.0/extras/testUnion3_ok.py similarity index 100% rename from python/test-data/testUnion3.py rename to python/test-data-2.0/extras/testUnion3_ok.py diff --git a/python/test-data/testUnion2.err b/python/test-data-2.0/extras/testUnionLiteral_ok.err similarity index 100% rename from python/test-data/testUnion2.err rename to python/test-data-2.0/extras/testUnionLiteral_ok.err diff --git a/python/test-data/testUnion3.err b/python/test-data-2.0/extras/testUnionLiteral_ok.out similarity index 100% rename from python/test-data/testUnion3.err rename to python/test-data-2.0/extras/testUnionLiteral_ok.out diff --git a/python/test-data/testUnionLiteral.py b/python/test-data-2.0/extras/testUnionLiteral_ok.py similarity index 100% rename from python/test-data/testUnionLiteral.py rename to python/test-data-2.0/extras/testUnionLiteral_ok.py diff --git a/python/test-data/testUnionLiteral.err b/python/test-data-2.0/extras/testUnionOfUnion_ok.err similarity index 100% rename from python/test-data/testUnionLiteral.err rename to python/test-data-2.0/extras/testUnionOfUnion_ok.err diff --git a/python/test-data/testUnionOfUnion.out b/python/test-data-2.0/extras/testUnionOfUnion_ok.out similarity index 100% rename from python/test-data/testUnionOfUnion.out rename to python/test-data-2.0/extras/testUnionOfUnion_ok.out diff --git a/python/test-data/testUnionOfUnion.py b/python/test-data-2.0/extras/testUnionOfUnion_ok.py similarity index 100% rename from python/test-data/testUnionOfUnion.py rename to python/test-data-2.0/extras/testUnionOfUnion_ok.py diff --git a/python/test-data/testUnionLiteral.out b/python/test-data-2.0/extras/testUnion_ok.err similarity index 100% rename from python/test-data/testUnionLiteral.out rename to python/test-data-2.0/extras/testUnion_ok.err diff --git a/python/test-data/testUnion.out b/python/test-data-2.0/extras/testUnion_ok.out similarity index 100% rename from python/test-data/testUnion.out rename to python/test-data-2.0/extras/testUnion_ok.out diff --git a/python/test-data/testUnion.py b/python/test-data-2.0/extras/testUnion_ok.py similarity index 100% rename from python/test-data/testUnion.py rename to python/test-data-2.0/extras/testUnion_ok.py diff --git a/python/test-data/modules/A/main.py b/python/test-data/modules/A/main.py index f641d76c..b089d254 100644 --- a/python/test-data/modules/A/main.py +++ b/python/test-data/modules/A/main.py @@ -1,3 +1,3 @@ import mod -mod.foo(1) +print(mod.foo(1)) 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/testHintParentheses2.py b/python/test-data/testHintParentheses2.py index 6f05ce0d..98456c55 100644 --- a/python/test-data/testHintParentheses2.py +++ b/python/test-data/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/test-data/testHintParentheses3.py b/python/test-data/testHintParentheses3.py index 225f0b11..d93e038e 100644 --- a/python/test-data/testHintParentheses3.py +++ b/python/test-data/testHintParentheses3.py @@ -5,3 +5,5 @@ # Tests 'return' def foo() -> Union(list, str): pass + +foo() diff --git a/python/test-data/testHintParentheses4.py b/python/test-data/testHintParentheses4.py new file mode 100644 index 00000000..50cd6a52 --- /dev/null +++ b/python/test-data/testHintParentheses4.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() -> dict(int, str): + pass + +foo() diff --git a/python/test-data/testHintParentheses5.py b/python/test-data/testHintParentheses5.py new file mode 100644 index 00000000..8e36e34f --- /dev/null +++ b/python/test-data/testHintParentheses5.py @@ -0,0 +1,9 @@ +from __future__ import annotations +from wypp import * +# See https://github.com/skogsbaer/write-your-python-program/issues/61 + +@record +class C: + x: list() + +C(1) diff --git a/python/test-data/testHintParentheses6.py b/python/test-data/testHintParentheses6.py new file mode 100644 index 00000000..17406ca9 --- /dev/null +++ b/python/test-data/testHintParentheses6.py @@ -0,0 +1,10 @@ +from __future__ import annotations +from wypp import * +# See https://github.com/skogsbaer/write-your-python-program/issues/61 + +@record(mutable=True) +class C: + x: list(int) + +c = C([]) +c.x = 1 diff --git a/python/test-data/testInvalidLiteral.py b/python/test-data/testInvalidLiteral.py index 3ef2938e..20a311a3 100644 --- a/python/test-data/testInvalidLiteral.py +++ b/python/test-data/testInvalidLiteral.py @@ -4,3 +4,5 @@ Game = list[list[Mark]] def gameFull(game:Game)->bool: pass + +gameFull([['x']]) 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/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/testTypesDict1.py b/python/test-data/testTypesDict1_ok.py similarity index 100% rename from python/test-data/testTypesDict1.py rename to python/test-data/testTypesDict1_ok.py diff --git a/python/test-data/testTypesHigherOrderFuns3.py b/python/test-data/testTypesHigherOrderFuns3.py index 15319d0a..4777f85c 100644 --- a/python/test-data/testTypesHigherOrderFuns3.py +++ b/python/test-data/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/test-data/testTypesSet1.py b/python/test-data/testTypesSet1_ok.py similarity index 100% rename from python/test-data/testTypesSet1.py rename to python/test-data/testTypesSet1_ok.py diff --git a/python/test-data/testUnionOfUnion.err b/python/test-data/testUnionOfUnion.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testWrap.err b/python/test-data/testWrap.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testWrap.out b/python/test-data/testWrap.out deleted file mode 100644 index c5277e9c..00000000 --- a/python/test-data/testWrap.out +++ /dev/null @@ -1 +0,0 @@ -Street(name='Hauptstraße', cars=(Car(licensePlate='OG PY 123', color='rot'),)) diff --git a/python/test-data/testWrap.py b/python/test-data/testWrap.py deleted file mode 100644 index f21cc68d..00000000 --- a/python/test-data/testWrap.py +++ /dev/null @@ -1,28 +0,0 @@ -from wypp import * - -@record -class Car: - licensePlate: str - color: str - -@record -class Street: - name: str - cars: tuple[Car, ...] # Tuple ist unveränderbar - -def removeElem(seq: Sequence, x: Any) -> Sequence: - i = seq.index(x) - return seq[:i] + seq[i+1:] - -rotesAuto = Car('OG PY 123', 'rot') -blauesAuto = Car(licensePlate='OG HS 130', color='blau') -grünesAuto = Car('FR XJ 252', 'grün') - -hauptstraße = Street('Hauptstraße', (rotesAuto, blauesAuto)) - -def leaveStreet(street: Street, car: Car) -> Street: - newCars = removeElem(street.cars, car) - return Street(street.name, newCars) - -s = leaveStreet(hauptstraße, blauesAuto) -print(s) diff --git a/python/test-data/testWrap2.err b/python/test-data/testWrap2.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testWrap2.out b/python/test-data/testWrap2.out deleted file mode 100644 index ef9a4b5d..00000000 --- a/python/test-data/testWrap2.out +++ /dev/null @@ -1 +0,0 @@ -Street(name='Hauptstraße', cars=[Car(licensePlate='OG PY 123', color='rot')]) diff --git a/python/test-data/testWrap2.py b/python/test-data/testWrap2.py deleted file mode 100644 index 90bfb494..00000000 --- a/python/test-data/testWrap2.py +++ /dev/null @@ -1,28 +0,0 @@ -from wypp import * - -@record -class Car: - licensePlate: str - color: str - -@record -class Street: - name: str - cars: list[Car] - -def removeElem(seq: Sequence, x: Any) -> Sequence: - i = seq.index(x) - return seq[:i] + seq[i+1:] - -rotesAuto = Car('OG PY 123', 'rot') -blauesAuto = Car(licensePlate='OG HS 130', color='blau') -grünesAuto = Car('FR XJ 252', 'grün') - -hauptstraße = Street('Hauptstraße', [rotesAuto, blauesAuto]) - -def leaveStreet(street: Street, car: Car) -> Street: - newCars = removeElem(street.cars, car) - return Street(street.name, newCars) - -s = leaveStreet(hauptstraße, blauesAuto) -print(s) diff --git a/python/test-data/testWrapperError.err b/python/test-data/testWrapperError.err deleted file mode 100644 index eec6efcd..00000000 --- a/python/test-data/testWrapperError.err +++ /dev/null @@ -1,8 +0,0 @@ -Traceback (most recent call last): - File "test-data/testWrapperError.py", line 9, in - foo(bar) - File "test-data/testWrapperError.py", line 4, in foo - return f(42) - File "test-data/testWrapperError.py", line 7, in bar - raise ValueError(str(i)) -ValueError: 42 diff --git a/python/test-data/testWrapperError.out b/python/test-data/testWrapperError.out deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testWrapperError.py b/python/test-data/testWrapperError.py deleted file mode 100644 index d01b874f..00000000 --- a/python/test-data/testWrapperError.py +++ /dev/null @@ -1,9 +0,0 @@ -from wypp import * - -def foo(f: Callable[[int], int]) -> int: - return f(42) - -def bar(i: int) -> int: - raise ValueError(str(i)) - -foo(bar) From de7bcfac8236b594afdf5ab6f7aa4dab6c51e6a7 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 12:54:46 +0200 Subject: [PATCH 39/56] typeguard: delete frame after use --- python/deps/typeguard/_functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/deps/typeguard/_functions.py b/python/deps/typeguard/_functions.py index e3a2622d..528d41bb 100644 --- a/python/deps/typeguard/_functions.py +++ b/python/deps/typeguard/_functions.py @@ -108,6 +108,7 @@ def check_type( 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: From 4bb2d74f1baf26dd94516f81d95fb1a7d963c6f6 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 12:55:22 +0200 Subject: [PATCH 40/56] most tests are working now --- python/TODO_nowrappers.md | 25 ++++ python/fileTests-2.0.py | 8 +- python/fileTests.py | 32 +++-- python/fileTestsLib.py | 28 ++-- python/src/errors.py | 27 +++- python/src/i18n.py | 13 +- python/src/instrument.py | 45 +++++- python/src/location.py | 32 ++++- python/src/myLogging.py | 5 + python/src/myTypeguard.py | 9 +- python/src/parsecache.py | 109 ++++++++++++--- python/src/records.py | 27 ++-- python/src/renderTy.py | 7 +- python/src/runner.py | 17 ++- python/src/stacktrace.py | 28 +++- python/src/typecheck.py | 78 +++++------ python/src/utils.py | 3 + python/src/writeYourProgram.py | 12 +- python/test-data-2.0/basics/async.err | 25 ++++ python/test-data-2.0/basics/async.err_en | 25 ++++ .../basics/async.out} | 0 .../basics/async.out_en} | 0 python/test-data-2.0/basics/async.py | 10 ++ python/test-data-2.0/basics/constructor.err | 2 +- .../test-data-2.0/basics/constructor.err_en | 2 +- python/test-data-2.0/basics/forwardRefs.err | 2 +- .../test-data-2.0/basics/forwardRefs.err_en | 2 +- python/test-data-2.0/basics/functionArg.err | 2 +- .../test-data-2.0/basics/functionArg.err_en | 2 +- .../test-data-2.0/basics/functionNoResult.err | 4 +- .../basics/functionNoResult.err_en | 4 +- .../basics/functionNoResult2.err | 4 +- .../basics/functionNoResult2.err_en | 4 +- .../basics/functionNoResult3.err | 4 +- .../basics/functionNoResult3.err_en | 4 +- .../test-data-2.0/basics/functionResult.err | 4 +- .../basics/functionResult.err_en | 4 +- .../test-data-2.0/basics/functionResult2.err | 4 +- .../basics/functionResult2.err_en | 4 +- python/test-data-2.0/basics/iterator.err | 4 +- python/test-data-2.0/basics/iterator.err_en | 4 +- python/test-data-2.0/basics/kwargs.err | 17 +++ python/test-data-2.0/basics/kwargs.err_en | 17 +++ .../basics/kwargs.out} | 0 .../basics/kwargs.out_en} | 0 python/test-data-2.0/basics/kwargs.py | 4 + python/test-data-2.0/basics/kwargs2.err | 17 +++ python/test-data-2.0/basics/kwargs2.err_en | 17 +++ .../basics/kwargs2.out} | 0 .../basics/kwargs2.out_en} | 0 python/test-data-2.0/basics/kwargs2.py | 4 + .../basics/kwargs_ok.err} | 0 python/test-data-2.0/basics/kwargs_ok.out | 1 + python/test-data-2.0/basics/kwargs_ok.py | 5 + python/test-data-2.0/basics/listArg.err | 2 +- python/test-data-2.0/basics/listArg.err_en | 2 +- python/test-data-2.0/basics/listResult.err | 4 +- python/test-data-2.0/basics/listResult.err_en | 4 +- python/test-data-2.0/basics/method.err | 2 +- python/test-data-2.0/basics/method.err_en | 2 +- python/test-data-2.0/basics/mutable.err | 2 +- python/test-data-2.0/basics/mutable.err_en | 2 +- python/test-data-2.0/basics/mutable2.err | 2 +- python/test-data-2.0/basics/mutable2.err_en | 2 +- python/test-data-2.0/basics/nested.err | 2 +- python/test-data-2.0/basics/nestedFun.err | 19 +++ python/test-data-2.0/basics/nestedFun.err_en | 19 +++ .../basics/nestedFun.out} | 0 .../basics/nestedFun.out_en} | 0 python/test-data-2.0/basics/nestedFun.py | 9 ++ python/test-data-2.0/basics/optionalArgs.err | 2 +- .../test-data-2.0/basics/optionalArgs.err_en | 2 +- python/test-data-2.0/basics/optionalArgs2.err | 2 +- .../test-data-2.0/basics/optionalArgs2.err_en | 2 +- python/test-data-2.0/basics/optionalArgs3.err | 2 +- .../test-data-2.0/basics/optionalArgs3.err_en | 2 +- python/test-data-2.0/basics/optionalArgs4.err | 6 +- .../test-data-2.0/basics/optionalArgs4.err_en | 6 +- python/test-data-2.0/basics/optionalAttr.err | 2 +- .../test-data-2.0/basics/optionalAttr.err_en | 2 +- python/test-data-2.0/basics/optionalAttr2.err | 2 +- .../test-data-2.0/basics/optionalAttr2.err_en | 2 +- python/test-data-2.0/basics/optionalAttr3.err | 2 +- .../test-data-2.0/basics/optionalAttr3.err_en | 2 +- python/test-data-2.0/basics/partial.err | 2 +- python/test-data-2.0/basics/partial.err_en | 2 +- python/test-data-2.0/basics/record.err | 2 +- python/test-data-2.0/basics/record.err_en | 2 +- .../basics/sample_ok.err} | 0 python/test-data-2.0/basics/sample_ok.out | 1 + python/test-data-2.0/basics/sample_ok.py | 132 ++++++++++++++++++ python/test-data-2.0/basics/stack.err | 10 +- python/test-data-2.0/basics/staticmethod.err | 2 +- .../test-data-2.0/basics/staticmethod.err_en | 2 +- python/test-data-2.0/basics/testCallable.err | 2 +- python/test-data-2.0/basics/tooFewArgs.err | 2 +- python/test-data-2.0/basics/tooFewAttrs.err | 2 +- python/test-data-2.0/basics/tooManyArgs.err | 2 +- .../test-data-2.0/basics/tooManyArgs.err_en | 2 +- python/test-data-2.0/basics/tooManyAttrs.err | 2 +- .../test-data-2.0/basics/tooManyAttrs.err_en | 2 +- python/test-data-2.0/extras/testTypes2.err | 2 +- .../failing}/declared-at-missing.err | 2 +- .../failing}/declared-at-missing.err-3.10 | 0 .../failing}/declared-at-missing.err-3.11 | 0 .../failing/declared-at-missing.err-3.12 | 16 +++ .../failing/declared-at-missing.out} | 0 .../failing}/declared-at-missing.py | 0 .../A => test-data-2.0/failing}/main.err | 2 +- .../failing/main.out} | 0 .../A => test-data-2.0/failing}/main.py | 0 .../failing}/testABCMeta.err | 2 +- .../failing}/testABCMeta.err-3.10 | 0 .../failing}/testABCMeta.err-3.11 | 0 .../failing/testABCMeta.out} | 0 .../failing}/testABCMeta.py | 0 .../failing/testArgs_ok.err} | 0 python/test-data-2.0/failing/testArgs_ok.out | 1 + python/test-data-2.0/failing/testArgs_ok.py | 4 + .../failing/testCheck_ok.err} | 0 python/test-data-2.0/failing/testCheck_ok.out | 5 + .../failing/testCheck_ok.py} | 0 .../failing/testDeepEqBug_ok.err} | 0 .../failing/testDeepEqBug_ok.out | 2 + .../failing/testDeepEqBug_ok.py} | 0 .../test-data-2.0/failing/testForwardRef2.err | 10 ++ .../failing/testForwardRef2.out} | 0 .../failing}/testForwardRef2.py | 0 .../failing}/testForwardRef4.err | 2 +- .../failing}/testForwardRef4.err-3.10 | 0 .../failing}/testForwardRef4.err-3.11 | 0 .../failing/testForwardRef4.err-3.12 | 10 ++ .../failing/testForwardRef4.out} | 0 .../failing}/testForwardRef4.py | 0 .../failing}/testForwardRef5.err | 2 +- .../failing}/testForwardRef5.err-3.10 | 0 .../failing}/testForwardRef5.err-3.11 | 0 .../failing/testForwardRef5.err-3.12 | 16 +++ .../failing/testForwardRef5.out} | 0 .../failing}/testForwardRef5.py | 0 .../failing/testFunEq_ok.err} | 0 python/test-data-2.0/failing/testFunEq_ok.out | 2 + .../failing/testFunEq_ok.py} | 0 .../failing}/testGetSource.err | 2 +- .../failing/testGetSource.out} | 0 .../failing}/testGetSource.py | 0 .../failing/testHintParentheses1.err | 10 ++ .../failing/testHintParentheses1.out} | 0 .../failing}/testHintParentheses1.py | 0 .../failing/testHintParentheses2.err | 10 ++ .../failing/testHintParentheses2.out} | 0 .../failing}/testHintParentheses2.py | 0 .../failing}/testHintParentheses3.err | 2 +- .../failing/testHintParentheses3.out} | 0 .../failing}/testHintParentheses3.py | 0 .../test-data-2.0/failing/testImpossible.err | 7 + .../failing/testImpossible.out} | 0 .../failing}/testImpossible.py | 0 .../test-data-2.0/failing/testIndexError.err | 6 + .../failing/testIndexError.out} | 0 .../failing}/testIndexError.py | 0 .../failing/testInvalidLiteral.err | 10 ++ .../failing/testInvalidLiteral.out} | 0 .../failing}/testInvalidLiteral.py | 0 .../failing/testIterable7_ok.err} | 0 .../failing/testIterable7_ok.out | 3 + .../failing/testIterable7_ok.py} | 0 .../failing/testIterableImplicitAny.err | 17 +++ .../failing}/testIterableImplicitAny.out | 0 .../failing}/testIterableImplicitAny.py | 0 .../failing}/testLiteral1.err | 2 +- .../failing/testLiteral1.out} | 0 .../failing}/testLiteral1.py | 0 .../test-data-2.0/failing/testLockFactory.err | 17 +++ .../failing/testLockFactory.out} | 0 .../failing}/testLockFactory.py | 0 .../failing/testLockFactory2.err | 17 +++ .../failing/testLockFactory2.out} | 0 .../failing}/testLockFactory2.py | 0 .../failing/testLockFactory_ok.err} | 0 .../failing/testLockFactory_ok.out | 1 + .../failing/testLockFactory_ok.py | 14 ++ .../failing/testMissingReturn.err | 19 +++ .../failing/testMissingReturn.out} | 0 .../failing}/testMissingReturn.py | 0 .../failing/testOriginalTypeNames_ok.err} | 0 .../failing/testOriginalTypeNames_ok.out | 1 + .../failing/testOriginalTypeNames_ok.py} | 0 .../failing}/testRecordSetTypeForwardRef.err | 4 +- .../testRecordSetTypeForwardRef.err-3.10 | 0 .../testRecordSetTypeForwardRef.err-3.11 | 0 .../testRecordSetTypeForwardRef.err-3.12 | 19 +++ .../failing/testRecordSetTypeForwardRef.out} | 0 .../failing}/testRecordSetTypeForwardRef.py | 0 .../failing}/testRecordSetTypes.err | 4 +- .../failing}/testRecordSetTypes.err-3.10 | 0 .../failing}/testRecordSetTypes.err-3.11 | 0 .../failing/testRecordSetTypes.err-3.12 | 19 +++ .../failing/testRecordSetTypes.out} | 0 .../failing}/testRecordSetTypes.py | 0 .../failing}/testRecordTypes.err | 2 +- .../failing/testRecordTypes.err-3.12 | 17 +++ .../failing/testRecordTypes.out} | 0 .../failing}/testRecordTypes.py | 0 python/test-data-2.0/failing/testTodo.err | 7 + .../failing/testTodo.out} | 0 .../failing}/testTodo.py | 0 .../test-data-2.0/failing/testTraceback.err | 6 + .../failing}/testTraceback.out | 0 .../failing}/testTraceback.py | 0 .../failing}/testTraceback2.err | 2 +- .../failing}/testTraceback2.err-3.10.0 | 0 .../failing}/testTraceback2.err-3.9 | 0 .../failing/testTraceback2.out} | 0 .../failing}/testTraceback2.py | 0 .../failing}/testTraceback3.err | 2 +- .../failing/testTraceback3.out} | 0 .../failing}/testTraceback3.py | 0 python/test-data-2.0/failing/testTypes1.err | 17 +++ .../failing}/testTypes1.err-notypes | 0 .../failing/testTypes1.out} | 0 .../failing}/testTypes1.py | 0 .../failing}/testTypesDict1.err | 4 +- .../failing/testTypesDict1.out} | 0 .../test-data-2.0/failing/testTypesDict3.err | 22 +++ .../failing/testTypesDict3.out} | 0 .../failing}/testTypesDict3.py | 0 .../test-data-2.0/failing/testTypesDict4.err | 24 ++++ .../failing/testTypesDict4.out} | 0 .../failing}/testTypesDict4.py | 0 .../failing/testTypesHigherOrderFuns.err | 22 +++ .../failing}/testTypesHigherOrderFuns.out | 0 .../failing}/testTypesHigherOrderFuns.py | 0 .../failing/testTypesHigherOrderFuns3.err | 17 +++ .../failing/testTypesHigherOrderFuns3.out} | 0 .../failing}/testTypesHigherOrderFuns3.py | 0 .../failing/testTypesProtos1.err | 19 +++ .../failing/testTypesProtos1.out} | 0 .../failing}/testTypesProtos1.py | 0 .../failing/testTypesProtos2.err | 19 +++ .../failing/testTypesProtos2.out} | 0 .../failing}/testTypesProtos2.py | 0 .../failing}/testTypesProtos3.err | 2 +- .../failing/testTypesProtos3.out} | 0 .../failing}/testTypesProtos3.py | 0 .../failing/testTypesProtos4.err | 21 +++ .../failing}/testTypesProtos4.out | 0 .../failing}/testTypesProtos4.py | 0 .../failing/testTypesProtos6.err | 25 ++++ .../failing/testTypesProtos6.out} | 0 .../failing}/testTypesProtos6.py | 0 .../failing/testTypesProtos7.err | 25 ++++ .../failing/testTypesProtos7.out | 0 .../failing}/testTypesProtos7.py | 0 .../failing/testTypesProtos8.err | 19 +++ .../failing/testTypesProtos8.out | 0 .../failing}/testTypesProtos8.py | 0 .../failing/testTypesProtos9.err | 19 +++ .../failing/testTypesProtos9.out | 0 .../failing}/testTypesProtos9.py | 0 .../failing}/testTypesRecordInheritance.err | 2 +- .../testTypesRecordInheritance.err-3.10 | 0 .../testTypesRecordInheritance.err-3.11 | 0 .../testTypesRecordInheritance.err-3.12 | 17 +++ .../failing}/testTypesRecordInheritance.out | 0 .../failing}/testTypesRecordInheritance.py | 0 .../test-data-2.0/failing/testTypesReturn.err | 23 +++ .../failing}/testTypesReturn.out | 0 .../failing}/testTypesReturn.py | 0 .../failing/testTypesSequence1.err | 17 +++ .../failing}/testTypesSequence1.out | 0 .../failing}/testTypesSequence1.py | 0 .../failing/testTypesSequence2.err | 17 +++ .../failing}/testTypesSequence2.out | 2 - .../failing}/testTypesSequence2.py | 0 .../failing}/testTypesSet1.err | 4 +- .../test-data-2.0/failing/testTypesSet1.out | 0 .../test-data-2.0/failing/testTypesSet2.err | 22 +++ .../failing}/testTypesSet2.out | 0 .../failing}/testTypesSet2.py | 0 .../failing/testTypesSubclassing1.err | 19 +++ .../failing/testTypesSubclassing1.out | 0 .../failing}/testTypesSubclassing1.py | 0 .../test-data-2.0/failing/testTypesTuple1.err | 17 +++ .../failing}/testTypesTuple1.out | 0 .../failing}/testTypesTuple1.py | 0 .../failing}/testWrongKeywordArg.err | 2 +- .../failing/testWrongKeywordArg.out | 0 .../failing}/testWrongKeywordArg.py | 0 .../failing}/testWrongKeywordArg2.err | 2 +- .../failing}/testWrongKeywordArg2.err-3.10 | 0 .../failing}/testWrongKeywordArg2.err-3.11 | 0 .../failing}/testWrongKeywordArg2.err-3.12 | 0 .../failing/testWrongKeywordArg2.out | 0 .../failing}/testWrongKeywordArg2.py | 0 .../failing/testWrongNumOfArguments.err | 13 ++ .../failing/testWrongNumOfArguments.out | 0 .../failing}/testWrongNumOfArguments.py | 0 .../failing/testWrongNumOfArguments2.err | 13 ++ .../failing/testWrongNumOfArguments2.out | 0 .../failing}/testWrongNumOfArguments2.py | 0 .../test-data-2.0/failing/wrong-caused-by.err | 17 +++ .../test-data-2.0/failing/wrong-caused-by.out | 0 .../failing}/wrong-caused-by.py | 0 python/test-data/declared-at-missing.err-3.12 | 12 -- python/test-data/testArgs.out | 1 - python/test-data/testArgs.py | 3 - python/test-data/testCheck.out | 5 - python/test-data/testDeepEqBug.out | 2 - python/test-data/testForwardRef2.err | 6 - python/test-data/testForwardRef4.err-3.12 | 6 - python/test-data/testForwardRef5.err-3.12 | 12 -- python/test-data/testFunEq.out | 2 - python/test-data/testHintParentheses1.err | 10 -- python/test-data/testHintParentheses2.err | 10 -- python/test-data/testImpossible.err | 4 - python/test-data/testIndexError.err | 6 - python/test-data/testInvalidLiteral.err | 5 - python/test-data/testIterable7.err | 12 -- python/test-data/testIterable7.out | 1 - python/test-data/testIterableImplicitAny.err | 12 -- python/test-data/testLockFactory.err | 12 -- python/test-data/testLockFactory2.err | 12 -- python/test-data/testMissingReturn.err | 14 -- python/test-data/testOriginalTypeNames.out | 1 - .../testRecordSetTypeForwardRef.err-3.12 | 12 -- python/test-data/testRecordSetTypes.err-3.12 | 12 -- python/test-data/testRecordTypes.err-3.12 | 12 -- python/test-data/testTodo.err | 4 - python/test-data/testTraceback.err | 6 - python/test-data/testTypes1.err | 12 -- python/test-data/testTypesCollections1.err | 14 -- python/test-data/testTypesCollections1.py | 8 -- python/test-data/testTypesCollections2.err | 14 -- python/test-data/testTypesCollections2.out | 1 - python/test-data/testTypesCollections2.py | 10 -- python/test-data/testTypesDict2.err | 14 -- python/test-data/testTypesDict2.out | 1 - python/test-data/testTypesDict2.py | 10 -- python/test-data/testTypesDict3.err | 14 -- python/test-data/testTypesDict4.err | 16 --- python/test-data/testTypesHigherOrderFuns.err | 14 -- .../test-data/testTypesHigherOrderFuns3.err | 17 --- python/test-data/testTypesProtos1.err | 27 ---- python/test-data/testTypesProtos2.err | 12 -- python/test-data/testTypesProtos4.err | 16 --- python/test-data/testTypesProtos6.err | 34 ----- python/test-data/testTypesProtos7.err | 33 ----- python/test-data/testTypesProtos8.err | 28 ---- python/test-data/testTypesProtos9.err | 27 ---- .../testTypesRecordInheritance.err-3.12 | 12 -- python/test-data/testTypesReturn.err | 12 -- python/test-data/testTypesSequence1.err | 12 -- python/test-data/testTypesSequence2.err | 14 -- python/test-data/testTypesSet2.err | 14 -- python/test-data/testTypesSet3.err | 14 -- python/test-data/testTypesSet3.py | 11 -- python/test-data/testTypesSet4.err | 14 -- python/test-data/testTypesSet4.out | 1 - python/test-data/testTypesSet4.py | 11 -- python/test-data/testTypesSubclassing1.err | 28 ---- python/test-data/testTypesTuple1.err | 12 -- python/test-data/testWrongNumOfArguments.err | 9 -- python/test-data/testWrongNumOfArguments2.err | 9 -- python/test-data/wrong-caused-by.err | 12 -- python/tests/locationTestData.py | 11 ++ python/tests/parsecacheTestData.py | 25 ++++ python/tests/sample.py | 26 ++-- python/tests/test_location.py | 14 ++ python/tests/test_parsecache.py | 52 ++++--- python/tests/test_record.py | 15 +- python/tests/test_sample.py | 22 --- python/tests/test_stacktrace.py | 10 +- python/tests/test_utils.py | 12 ++ 374 files changed, 1602 insertions(+), 1011 deletions(-) create mode 100644 python/test-data-2.0/basics/async.err create mode 100644 python/test-data-2.0/basics/async.err_en rename python/{test-data/declared-at-missing.out => test-data-2.0/basics/async.out} (100%) rename python/{test-data/modules/A/main.out => test-data-2.0/basics/async.out_en} (100%) create mode 100644 python/test-data-2.0/basics/async.py create mode 100644 python/test-data-2.0/basics/kwargs.err create mode 100644 python/test-data-2.0/basics/kwargs.err_en rename python/{test-data/testABCMeta.out => test-data-2.0/basics/kwargs.out} (100%) rename python/{test-data/testArgs.err => test-data-2.0/basics/kwargs.out_en} (100%) create mode 100644 python/test-data-2.0/basics/kwargs.py create mode 100644 python/test-data-2.0/basics/kwargs2.err create mode 100644 python/test-data-2.0/basics/kwargs2.err_en rename python/{test-data/testCheck.err => test-data-2.0/basics/kwargs2.out} (100%) rename python/{test-data/testDeepEqBug.err => test-data-2.0/basics/kwargs2.out_en} (100%) create mode 100644 python/test-data-2.0/basics/kwargs2.py rename python/{test-data/testForwardRef2.out => test-data-2.0/basics/kwargs_ok.err} (100%) create mode 100644 python/test-data-2.0/basics/kwargs_ok.out create mode 100644 python/test-data-2.0/basics/kwargs_ok.py create mode 100644 python/test-data-2.0/basics/nestedFun.err create mode 100644 python/test-data-2.0/basics/nestedFun.err_en rename python/{test-data/testForwardRef4.out => test-data-2.0/basics/nestedFun.out} (100%) rename python/{test-data/testForwardRef5.out => test-data-2.0/basics/nestedFun.out_en} (100%) create mode 100644 python/test-data-2.0/basics/nestedFun.py rename python/{test-data/testFunEq.err => test-data-2.0/basics/sample_ok.err} (100%) create mode 100644 python/test-data-2.0/basics/sample_ok.out create mode 100644 python/test-data-2.0/basics/sample_ok.py rename python/{test-data => test-data-2.0/failing}/declared-at-missing.err (90%) rename python/{test-data => test-data-2.0/failing}/declared-at-missing.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/declared-at-missing.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/declared-at-missing.err-3.12 rename python/{test-data/testGetSource.out => test-data-2.0/failing/declared-at-missing.out} (100%) rename python/{test-data => test-data-2.0/failing}/declared-at-missing.py (100%) rename python/{test-data/modules/A => test-data-2.0/failing}/main.err (84%) rename python/{test-data/testHintParentheses1.out => test-data-2.0/failing/main.out} (100%) rename python/{test-data/modules/A => test-data-2.0/failing}/main.py (100%) rename python/{test-data => test-data-2.0/failing}/testABCMeta.err (74%) rename python/{test-data => test-data-2.0/failing}/testABCMeta.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testABCMeta.err-3.11 (100%) rename python/{test-data/testHintParentheses2.out => test-data-2.0/failing/testABCMeta.out} (100%) rename python/{test-data => test-data-2.0/failing}/testABCMeta.py (100%) rename python/{test-data/testHintParentheses3.out => test-data-2.0/failing/testArgs_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testArgs_ok.out create mode 100644 python/test-data-2.0/failing/testArgs_ok.py rename python/{test-data/testImpossible.out => test-data-2.0/failing/testCheck_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testCheck_ok.out rename python/{test-data/testCheck.py => test-data-2.0/failing/testCheck_ok.py} (100%) rename python/{test-data/testIndexError.out => test-data-2.0/failing/testDeepEqBug_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testDeepEqBug_ok.out rename python/{test-data/testDeepEqBug.py => test-data-2.0/failing/testDeepEqBug_ok.py} (100%) create mode 100644 python/test-data-2.0/failing/testForwardRef2.err rename python/{test-data/testInvalidLiteral.out => test-data-2.0/failing/testForwardRef2.out} (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef2.py (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef4.err (77%) rename python/{test-data => test-data-2.0/failing}/testForwardRef4.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef4.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/testForwardRef4.err-3.12 rename python/{test-data/testLiteral1.out => test-data-2.0/failing/testForwardRef4.out} (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef4.py (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef5.err (87%) rename python/{test-data => test-data-2.0/failing}/testForwardRef5.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef5.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/testForwardRef5.err-3.12 rename python/{test-data/testLockFactory.out => test-data-2.0/failing/testForwardRef5.out} (100%) rename python/{test-data => test-data-2.0/failing}/testForwardRef5.py (100%) rename python/{test-data/testLockFactory2.out => test-data-2.0/failing/testFunEq_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testFunEq_ok.out rename python/{test-data/testFunEq.py => test-data-2.0/failing/testFunEq_ok.py} (100%) rename python/{test-data => test-data-2.0/failing}/testGetSource.err (77%) rename python/{test-data/testMissingReturn.out => test-data-2.0/failing/testGetSource.out} (100%) rename python/{test-data => test-data-2.0/failing}/testGetSource.py (100%) create mode 100644 python/test-data-2.0/failing/testHintParentheses1.err rename python/{test-data/testOriginalTypeNames.err => test-data-2.0/failing/testHintParentheses1.out} (100%) rename python/{test-data => test-data-2.0/failing}/testHintParentheses1.py (100%) create mode 100644 python/test-data-2.0/failing/testHintParentheses2.err rename python/{test-data/testRecordSetTypeForwardRef.out => test-data-2.0/failing/testHintParentheses2.out} (100%) rename python/{test-data => test-data-2.0/failing}/testHintParentheses2.py (100%) rename python/{test-data => test-data-2.0/failing}/testHintParentheses3.err (86%) rename python/{test-data/testRecordSetTypes.out => test-data-2.0/failing/testHintParentheses3.out} (100%) rename python/{test-data => test-data-2.0/failing}/testHintParentheses3.py (100%) create mode 100644 python/test-data-2.0/failing/testImpossible.err rename python/{test-data/testRecordTypes.out => test-data-2.0/failing/testImpossible.out} (100%) rename python/{test-data => test-data-2.0/failing}/testImpossible.py (100%) create mode 100644 python/test-data-2.0/failing/testIndexError.err rename python/{test-data/testTodo.out => test-data-2.0/failing/testIndexError.out} (100%) rename python/{test-data => test-data-2.0/failing}/testIndexError.py (100%) create mode 100644 python/test-data-2.0/failing/testInvalidLiteral.err rename python/{test-data/testTraceback2.out => test-data-2.0/failing/testInvalidLiteral.out} (100%) rename python/{test-data => test-data-2.0/failing}/testInvalidLiteral.py (100%) rename python/{test-data/testTraceback3.out => test-data-2.0/failing/testIterable7_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testIterable7_ok.out rename python/{test-data/testIterable7.py => test-data-2.0/failing/testIterable7_ok.py} (100%) create mode 100644 python/test-data-2.0/failing/testIterableImplicitAny.err rename python/{test-data => test-data-2.0/failing}/testIterableImplicitAny.out (100%) rename python/{test-data => test-data-2.0/failing}/testIterableImplicitAny.py (100%) rename python/{test-data => test-data-2.0/failing}/testLiteral1.err (73%) rename python/{test-data/testTypes1.out => test-data-2.0/failing/testLiteral1.out} (100%) rename python/{test-data => test-data-2.0/failing}/testLiteral1.py (100%) create mode 100644 python/test-data-2.0/failing/testLockFactory.err rename python/{test-data/testTypesCollections1.out => test-data-2.0/failing/testLockFactory.out} (100%) rename python/{test-data => test-data-2.0/failing}/testLockFactory.py (100%) create mode 100644 python/test-data-2.0/failing/testLockFactory2.err rename python/{test-data/testTypesDict1.out => test-data-2.0/failing/testLockFactory2.out} (100%) rename python/{test-data => test-data-2.0/failing}/testLockFactory2.py (100%) rename python/{test-data/testTypesDict3.out => test-data-2.0/failing/testLockFactory_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testLockFactory_ok.out create mode 100644 python/test-data-2.0/failing/testLockFactory_ok.py create mode 100644 python/test-data-2.0/failing/testMissingReturn.err rename python/{test-data/testTypesDict4.out => test-data-2.0/failing/testMissingReturn.out} (100%) rename python/{test-data => test-data-2.0/failing}/testMissingReturn.py (100%) rename python/{test-data/testTypesHigherOrderFuns3.out => test-data-2.0/failing/testOriginalTypeNames_ok.err} (100%) create mode 100644 python/test-data-2.0/failing/testOriginalTypeNames_ok.out rename python/{test-data/testOriginalTypeNames.py => test-data-2.0/failing/testOriginalTypeNames_ok.py} (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypeForwardRef.err (66%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypeForwardRef.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypeForwardRef.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 rename python/{test-data/testTypesProtos1.out => test-data-2.0/failing/testRecordSetTypeForwardRef.out} (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypeForwardRef.py (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypes.err (68%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypes.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypes.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/testRecordSetTypes.err-3.12 rename python/{test-data/testTypesProtos2.out => test-data-2.0/failing/testRecordSetTypes.out} (100%) rename python/{test-data => test-data-2.0/failing}/testRecordSetTypes.py (100%) rename python/{test-data => test-data-2.0/failing}/testRecordTypes.err (85%) create mode 100644 python/test-data-2.0/failing/testRecordTypes.err-3.12 rename python/{test-data/testTypesProtos3.out => test-data-2.0/failing/testRecordTypes.out} (100%) rename python/{test-data => test-data-2.0/failing}/testRecordTypes.py (100%) create mode 100644 python/test-data-2.0/failing/testTodo.err rename python/{test-data/testTypesProtos6.out => test-data-2.0/failing/testTodo.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTodo.py (100%) create mode 100644 python/test-data-2.0/failing/testTraceback.err rename python/{test-data => test-data-2.0/failing}/testTraceback.out (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback.py (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback2.err (58%) rename python/{test-data => test-data-2.0/failing}/testTraceback2.err-3.10.0 (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback2.err-3.9 (100%) rename python/{test-data/testTypesProtos7.out => test-data-2.0/failing/testTraceback2.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback2.py (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback3.err (61%) rename python/{test-data/testTypesProtos8.out => test-data-2.0/failing/testTraceback3.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTraceback3.py (100%) create mode 100644 python/test-data-2.0/failing/testTypes1.err rename python/{test-data => test-data-2.0/failing}/testTypes1.err-notypes (100%) rename python/{test-data/testTypesProtos9.out => test-data-2.0/failing/testTypes1.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypes1.py (100%) rename python/{test-data => test-data-2.0/failing}/testTypesDict1.err (74%) rename python/{test-data/testTypesSet1.out => test-data-2.0/failing/testTypesDict1.out} (100%) create mode 100644 python/test-data-2.0/failing/testTypesDict3.err rename python/{test-data/testTypesSet3.out => test-data-2.0/failing/testTypesDict3.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesDict3.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesDict4.err rename python/{test-data/testTypesSubclassing1.out => test-data-2.0/failing/testTypesDict4.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesDict4.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesHigherOrderFuns.err rename python/{test-data => test-data-2.0/failing}/testTypesHigherOrderFuns.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesHigherOrderFuns.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesHigherOrderFuns3.err rename python/{test-data/testWrongKeywordArg.out => test-data-2.0/failing/testTypesHigherOrderFuns3.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesHigherOrderFuns3.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos1.err rename python/{test-data/testWrongKeywordArg2.out => test-data-2.0/failing/testTypesProtos1.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos1.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos2.err rename python/{test-data/testWrongNumOfArguments.out => test-data-2.0/failing/testTypesProtos2.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos2.py (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos3.err (87%) rename python/{test-data/testWrongNumOfArguments2.out => test-data-2.0/failing/testTypesProtos3.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos3.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos4.err rename python/{test-data => test-data-2.0/failing}/testTypesProtos4.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos4.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos6.err rename python/{test-data/wrong-caused-by.out => test-data-2.0/failing/testTypesProtos6.out} (100%) rename python/{test-data => test-data-2.0/failing}/testTypesProtos6.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos7.err create mode 100644 python/test-data-2.0/failing/testTypesProtos7.out rename python/{test-data => test-data-2.0/failing}/testTypesProtos7.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos8.err create mode 100644 python/test-data-2.0/failing/testTypesProtos8.out rename python/{test-data => test-data-2.0/failing}/testTypesProtos8.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesProtos9.err create mode 100644 python/test-data-2.0/failing/testTypesProtos9.out rename python/{test-data => test-data-2.0/failing}/testTypesProtos9.py (100%) rename python/{test-data => test-data-2.0/failing}/testTypesRecordInheritance.err (85%) rename python/{test-data => test-data-2.0/failing}/testTypesRecordInheritance.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testTypesRecordInheritance.err-3.11 (100%) create mode 100644 python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 rename python/{test-data => test-data-2.0/failing}/testTypesRecordInheritance.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesRecordInheritance.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesReturn.err rename python/{test-data => test-data-2.0/failing}/testTypesReturn.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesReturn.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesSequence1.err rename python/{test-data => test-data-2.0/failing}/testTypesSequence1.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesSequence1.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesSequence2.err rename python/{test-data => test-data-2.0/failing}/testTypesSequence2.out (73%) rename python/{test-data => test-data-2.0/failing}/testTypesSequence2.py (100%) rename python/{test-data => test-data-2.0/failing}/testTypesSet1.err (73%) create mode 100644 python/test-data-2.0/failing/testTypesSet1.out create mode 100644 python/test-data-2.0/failing/testTypesSet2.err rename python/{test-data => test-data-2.0/failing}/testTypesSet2.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesSet2.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesSubclassing1.err create mode 100644 python/test-data-2.0/failing/testTypesSubclassing1.out rename python/{test-data => test-data-2.0/failing}/testTypesSubclassing1.py (100%) create mode 100644 python/test-data-2.0/failing/testTypesTuple1.err rename python/{test-data => test-data-2.0/failing}/testTypesTuple1.out (100%) rename python/{test-data => test-data-2.0/failing}/testTypesTuple1.py (100%) rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg.err (78%) create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg.out rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg.py (100%) rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg2.err (81%) rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg2.err-3.10 (100%) rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg2.err-3.11 (100%) rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg2.err-3.12 (100%) create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg2.out rename python/{test-data => test-data-2.0/failing}/testWrongKeywordArg2.py (100%) create mode 100644 python/test-data-2.0/failing/testWrongNumOfArguments.err create mode 100644 python/test-data-2.0/failing/testWrongNumOfArguments.out rename python/{test-data => test-data-2.0/failing}/testWrongNumOfArguments.py (100%) create mode 100644 python/test-data-2.0/failing/testWrongNumOfArguments2.err create mode 100644 python/test-data-2.0/failing/testWrongNumOfArguments2.out rename python/{test-data => test-data-2.0/failing}/testWrongNumOfArguments2.py (100%) create mode 100644 python/test-data-2.0/failing/wrong-caused-by.err create mode 100644 python/test-data-2.0/failing/wrong-caused-by.out rename python/{test-data => test-data-2.0/failing}/wrong-caused-by.py (100%) delete mode 100644 python/test-data/declared-at-missing.err-3.12 delete mode 100644 python/test-data/testArgs.out delete mode 100644 python/test-data/testArgs.py delete mode 100644 python/test-data/testCheck.out delete mode 100644 python/test-data/testDeepEqBug.out delete mode 100644 python/test-data/testForwardRef2.err delete mode 100644 python/test-data/testForwardRef4.err-3.12 delete mode 100644 python/test-data/testForwardRef5.err-3.12 delete mode 100644 python/test-data/testFunEq.out delete mode 100644 python/test-data/testHintParentheses1.err delete mode 100644 python/test-data/testHintParentheses2.err delete mode 100644 python/test-data/testImpossible.err delete mode 100644 python/test-data/testIndexError.err delete mode 100644 python/test-data/testInvalidLiteral.err delete mode 100644 python/test-data/testIterable7.err delete mode 100644 python/test-data/testIterable7.out delete mode 100644 python/test-data/testIterableImplicitAny.err delete mode 100644 python/test-data/testLockFactory.err delete mode 100644 python/test-data/testLockFactory2.err delete mode 100644 python/test-data/testMissingReturn.err delete mode 100644 python/test-data/testOriginalTypeNames.out delete mode 100644 python/test-data/testRecordSetTypeForwardRef.err-3.12 delete mode 100644 python/test-data/testRecordSetTypes.err-3.12 delete mode 100644 python/test-data/testRecordTypes.err-3.12 delete mode 100644 python/test-data/testTodo.err delete mode 100644 python/test-data/testTraceback.err delete mode 100644 python/test-data/testTypes1.err delete mode 100644 python/test-data/testTypesCollections1.err delete mode 100644 python/test-data/testTypesCollections1.py delete mode 100644 python/test-data/testTypesCollections2.err delete mode 100644 python/test-data/testTypesCollections2.out delete mode 100644 python/test-data/testTypesCollections2.py delete mode 100644 python/test-data/testTypesDict2.err delete mode 100644 python/test-data/testTypesDict2.out delete mode 100644 python/test-data/testTypesDict2.py delete mode 100644 python/test-data/testTypesDict3.err delete mode 100644 python/test-data/testTypesDict4.err delete mode 100644 python/test-data/testTypesHigherOrderFuns.err delete mode 100644 python/test-data/testTypesHigherOrderFuns3.err delete mode 100644 python/test-data/testTypesProtos1.err delete mode 100644 python/test-data/testTypesProtos2.err delete mode 100644 python/test-data/testTypesProtos4.err delete mode 100644 python/test-data/testTypesProtos6.err delete mode 100644 python/test-data/testTypesProtos7.err delete mode 100644 python/test-data/testTypesProtos8.err delete mode 100644 python/test-data/testTypesProtos9.err delete mode 100644 python/test-data/testTypesRecordInheritance.err-3.12 delete mode 100644 python/test-data/testTypesReturn.err delete mode 100644 python/test-data/testTypesSequence1.err delete mode 100644 python/test-data/testTypesSequence2.err delete mode 100644 python/test-data/testTypesSet2.err delete mode 100644 python/test-data/testTypesSet3.err delete mode 100644 python/test-data/testTypesSet3.py delete mode 100644 python/test-data/testTypesSet4.err delete mode 100644 python/test-data/testTypesSet4.out delete mode 100644 python/test-data/testTypesSet4.py delete mode 100644 python/test-data/testTypesSubclassing1.err delete mode 100644 python/test-data/testTypesTuple1.err delete mode 100644 python/test-data/testWrongNumOfArguments.err delete mode 100644 python/test-data/testWrongNumOfArguments2.err delete mode 100644 python/test-data/wrong-caused-by.err create mode 100644 python/tests/parsecacheTestData.py delete mode 100644 python/tests/test_sample.py create mode 100644 python/tests/test_utils.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index f95edf1b..a853b75e 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,5 +1,30 @@ * Fix file tests in test-data +* proper error messages for invalid @record +* Avoid absolute paths in error messages * Integration tests * Installation * Typechecked console * Debug slow startup times +* show "@record\nclass C" for record attributes + + +Problematic tests: + + test-data-2.0/failing/testTypesProtos3.py # non-static methods without self + + test-data-2.0/failing/testHintParentheses3.py # Union(list, str) + + test-data-2.0/failing/testWrongKeywordArg2.py # problem with keyword args + test-data-2.0/failing/testWrongKeywordArg.py # problem with keyword args + + test-data-2.0/failing/main.py # modules + + test-data-2.0/failing/testGetSource.py # Literal + test-data-2.0/failing/testLiteral1.py # Literal + + + + test-data-2.0/failing/testArgs_ok.py # absolute filenames + + + diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index 828e903c..cfdbc6c0 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -1,7 +1,13 @@ from pathlib import Path from fileTestsLib import * -directories = [Path("test-data-2.0/basics"), Path("test-data-2.0/extras")] +directories = [Path("test-data-2.0/basics"), + Path("test-data-2.0/extras"), + Path("test-data-2.0/failing")] + +#directories = [Path("test-data-2.0/basics")] +#directories = [Path("test-data-2.0/extras")] +#directories = [Path("test-data-2.0/failing")] for d in directories: for file in d.iterdir(): diff --git a/python/fileTests.py b/python/fileTests.py index e0e4ea79..55950358 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -1,9 +1,20 @@ -from fileTestsLib import * +# from fileTestsLib import * +import os + +#checkInstall('test-data/fileWithImport.py') +#checkInstall('test-data/fileWithoutImport.py') +#checkInstall('test-data/fileWithBothImports.py') +#checkInstall('test-data/fileWithRecursiveTypes.py') +def check(file, **kws): + base = os.path.basename(file)[:-3] + d = 'test-data-2.0/extras/' + cands = [d + base + '.py', d + base + '_ok.py'] + #print(cands) + if not any([os.path.isfile(x) for x in cands]): + print(file) +def checkBoth(file, **kws): + check(file) -checkInstall('test-data/fileWithImport.py') -checkInstall('test-data/fileWithoutImport.py') -checkInstall('test-data/fileWithBothImports.py') -checkInstall('test-data/fileWithRecursiveTypes.py') checkBoth('test-data/testTraceback.py') checkBoth('test-data/testTraceback2.py') checkBoth('test-data/testTraceback3.py') @@ -12,16 +23,13 @@ checkBoth('test-data/printModuleNameImport.py', exitCode=0) checkBoth('test-data/testTypes1.py') checkBoth('test-data/testIndexError.py') -checkBoth('test-data/testWrapperError.py') checkBoth('test-data/testCheckFail.py', exitCode=0) check('test-data/testTypes2.py', exitCode=1) check('test-data/testTypes2.py', exitCode=0, typecheck=False) check('test-data/testABCMeta.py') check('test-data/testClassHierarchy.py', exitCode=0) -check('test-data/testTypesCollections1.py') +check('test-data/testTypesCollections1.py', exitCode=0) check('test-data/testTypesCollections2.py') -check('test-data/testTypesCollections3.py') -check('test-data/testTypesCollections4.py') check('test-data/testTypesProtos1.py') check('test-data/testTypesProtos2.py') check('test-data/testTypesProtos3.py') @@ -93,7 +101,7 @@ check('test-data/testIterable4.py', exitCode=0) check('test-data/testIterable5.py', exitCode=0) check('test-data/testIterable6.py', exitCode=0) -check('test-data/testIterable7.py') +check('test-data/testIterable7.py', exitCode=0) check('test-data/testIterator.py', exitCode=0) check('test-data/testIterator2.py', exitCode=0) check('test-data/testIterator3.py', exitCode=0) @@ -102,8 +110,6 @@ check('test-data/testCopy.py', exitCode=0) check('test-data/testHof.py', exitCode=0) check('test-data/testIndexSeq.py', exitCode=0) -check('test-data/testWrap.py', exitCode=0) -check('test-data/testWrap2.py', exitCode=0) check('test-data/testTodo.py') check('test-data/testImpossible.py') check('test-data/testInvalidLiteral.py') @@ -122,5 +128,3 @@ #check('test-data/testDisappearingObject_02.py', exitCode=0) #check('test-data/testDisappearingObject_03.py', exitCode=0) checkBoth('test-data/testTypeKeyword.py', exitCode=0, minVersion=(3,12)) - -globalCtx.results.finish() diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 67587271..ba633550 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -195,7 +195,7 @@ def fixOutput(filePath: str): """ 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 "/[^"]*"', ' File ""', content, flags=re.MULTILINE) # Remove file paths + content = re.sub(r'File "[^"]*/([^/"]*)"', 'File "\\1"', content, flags=re.MULTILINE) # Remove file paths content = re.sub(r'## File .*/([^/]*)$', '## File \\1', content, flags=re.MULTILINE) content = re.sub(r'## Datei .*/([^/]*)$', '## Datei \\1', content, flags=re.MULTILINE) with open(filePath, 'w') as f: @@ -317,9 +317,10 @@ def guessExitCode(testFile: str) -> int: @dataclass class WyppTestConfig: typecheck: Literal[True, False, "both"] + args: list[str] @staticmethod def default() -> WyppTestConfig: - return WyppTestConfig(typecheck=True) + return WyppTestConfig(typecheck=True, args=[]) def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: """ @@ -327,7 +328,7 @@ def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: `max_lines` lines of the file at `path` and return it as a dict. Returns {} if not present. """ - validKeys = ['typecheck'] + validKeys = ['typecheck', 'args'] with open(path, "r", encoding="utf-8") as f: for lineno in range(1, max_lines + 1): line = f.readline() @@ -340,7 +341,9 @@ def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: for k in j: if k not in validKeys: raise ValueError(f'Unknown key {k} in config for file {path}') - return WyppTestConfig(typecheck=j['typecheck']) + typecheck = j.get('typecheck', True) + args = j.get('args', []) + return WyppTestConfig(typecheck=typecheck, args=args) return WyppTestConfig.default() def checkNoConfig(testFile: str, @@ -362,12 +365,12 @@ def checkNoConfig(testFile: str, def check(testFile: str, exitCode: int = 1, - args: list[str] = [], pythonPath: list[str] = [], minVersion: Optional[tuple[int, int]] = None, checkOutputs: bool = True, ctx: TestContext = globalCtx,): cfg = readWyppTestConfig(testFile) + args = cfg.args if cfg.typecheck == 'both': checkNoConfig(testFile, exitCode, typecheck=True, args=args, pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs, @@ -380,14 +383,6 @@ def check(testFile: str, pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs, ctx=ctx, what=' (no typecheck)') -def checkBoth(testFile: str, - exitCode: int = 1, - args: list[str] = [], - minVersion: Optional[tuple[int, int]] = None, - ctx: TestContext = globalCtx): - check(testFile, exitCode, typecheck=True, args=args, minVersion=minVersion, ctx=ctx, what=' (typecheck)') - check(testFile, exitCode, typecheck=False, args=args, minVersion=minVersion, ctx=ctx, what=' (no typecheck)') - def checkBasic(testFile: str, ctx: TestContext = globalCtx): check(testFile, checkOutputs=False, ctx=ctx) @@ -397,8 +392,11 @@ def record(testFile: str): """ baseFile = os.path.splitext(testFile)[0] exitCode = guessExitCode(testFile) - typecheck = True - args = [] + cfg = readWyppTestConfig(testFile) + typecheck = cfg.typecheck + if typecheck == 'both': + typecheck = True + args = cfg.args pythonPath = [] what = '' ctx = globalCtx diff --git a/python/src/errors.py b/python/src/errors.py index 31580fc9..fdfaba23 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -117,13 +117,17 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc raise WyppTypeError('\n'.join(lines), extraFrames) @staticmethod - def argumentError(callableName: location.CallableName, paramName: str, paramIndex: int, paramLoc: Optional[location.Loc], + 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('') - lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramIndex + 1)) + 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: @@ -223,7 +227,26 @@ def recordAssignError(recordName: str, 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/src/i18n.py b/python/src/i18n.py index 1070e52c..91216bac 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -100,7 +100,7 @@ def tr(key: str, **kws) -> str: 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation.', 'invalid type `{ty}`': - 'ungültiger 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', @@ -194,8 +194,15 @@ def transArg(pos: int): raise ValueError(f'Unexpected language: {l}') -def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int) -> str: - arg = transArg(pos) +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}.', diff --git a/python/src/instrument.py b/python/src/instrument.py index 8fd0ea05..f6e5acc6 100644 --- a/python/src/instrument.py +++ b/python/src/instrument.py @@ -20,20 +20,55 @@ def parseExp(s: str) -> ast.expr: raise ValueError(f'String {repr(s)} does not parse as an expression: {m}') class Configs: - funConfig: ast.expr = parseExp("{'kind': 'function'}") + funConfig: ast.expr = parseExp("{'kind': 'function', 'globals': globals(), 'locals': locals()}") @staticmethod def methodConfig(clsName: str) -> ast.expr: - return parseExp("{'kind': 'method', 'className': " + repr(clsName) + "}") + 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) -> ast.expr: + 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 _: + # print(ast.dump(e)) FIXME: proper error message + raise ValueError(f'Invalid record config') + case ast.Call(ast.Name('record'), _, _): + raise ValueError(f'Invalid record config') + case _: + return e def transformStmt(stmt: ast.stmt, outerClassName: Optional[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): - return ast.FunctionDef(name, args, body, decorators + [wrapExp], returns, tyComment, tyParams) - case ast.ClassDef(className, bases, keywords, body, decorator_list, type_params): + newBody = [transformStmt(s, outerClassName=outerClassName) 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) 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) for s in body] - return ast.ClassDef(className, bases, keywords, newBody, decorator_list, type_params) + newDecoratorList = [transformDecorator(e) for e in decoratorList] + x = ast.ClassDef(className, bases, keywords, newBody, newDecoratorList, type_params) + return transferLocs(stmt, x) case _: return stmt diff --git a/python/src/location.py b/python/src/location.py index 40b15dcd..4fa04b91 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -12,6 +12,7 @@ import sys import abc import parsecache +from parsecache import FunMatcher @dataclass class Loc: @@ -146,6 +147,7 @@ class StdCallableInfo(CallableInfo): 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) @@ -154,7 +156,14 @@ def name(self): return self.__name def _findDef(self) -> Optional[ast.FunctionDef | ast.AsyncFunctionDef]: - return self.__ast.getFunDef(self.__name) + 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]: """ @@ -239,7 +248,7 @@ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: else: return None -def locationOfArgument(fi: inspect.FrameInfo, idx: int) -> Optional[Loc]: +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. @@ -250,11 +259,22 @@ def locationOfArgument(fi: inspect.FrameInfo, idx: int) -> Optional[Loc]: codeOfCall = loc.code() if codeOfCall is None: return loc - tree = ast.parse(codeOfCall) + try: + tree = ast.parse(codeOfCall) + except SyntaxError: + return loc match tree: - case ast.Module([ast.Expr(ast.Call(_fun, args, _kwArgs))]): - if idx >= 0 and idx < len(args): - arg = args[idx] + 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 diff --git a/python/src/myLogging.py b/python/src/myLogging.py index bb1994dc..10faa332 100644 --- a/python/src/myLogging.py +++ b/python/src/myLogging.py @@ -8,6 +8,11 @@ def enableVerbose(): global VERBOSE VERBOSE = True +def enableDebug(): + global VERBOSE, DEBUG + VERBOSE = True + DEBUG = True + def printStderr(s=''): sys.stderr.write(s + '\n') diff --git a/python/src/myTypeguard.py b/python/src/myTypeguard.py index c8b230a5..b3854ee3 100644 --- a/python/src/myTypeguard.py +++ b/python/src/myTypeguard.py @@ -2,6 +2,7 @@ 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 @@ -32,7 +33,13 @@ def matchesTy(a: Any, ty: Any, ns: Namespaces) -> MatchesTyResult: 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: - return typeguard._utils.get_type_name(t, ['__wypp__']) + 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/src/parsecache.py b/python/src/parsecache.py index 8e05b540..94810c6e 100644 --- a/python/src/parsecache.py +++ b/python/src/parsecache.py @@ -1,6 +1,28 @@ 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: @@ -24,42 +46,95 @@ def __ensureTree(self): 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 getFunDef(self, name: str) -> ast.FunctionDef | ast.AsyncFunctionDef | None: - if name in self.__cache: - return self.__cache[name] + 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() - resultNode = None if self.__tree: - for node in ast.walk(self.__tree): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name: - resultNode = node - break - self.__cache[name] = resultNode + 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 = None - if self.__tree: - for node in ast.walk(self.__tree): - if isinstance(node, ast.ClassDef) and node.name == clsName: - targetCls = node - break + targetCls = self._findClass(clsName) # Step 2: look for an AnnAssign like: A: T = ... - annNode = None + annNodes = [] if targetCls: for stmt in targetCls.body: if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): if stmt.target.id == attrName: - annNode = stmt + 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: diff --git a/python/src/records.py b/python/src/records.py index ead2e8da..4598ac35 100644 --- a/python/src/records.py +++ b/python/src/records.py @@ -1,6 +1,6 @@ import typing import dataclasses -import inspect +import utils import sys import myTypeguard import errors @@ -24,13 +24,6 @@ def _collectDataClassAttributes(cls): result = c.__annotations__ | result return result -def _getNamespacesOfClass(cls): - mod = sys.modules.get(cls.__module__) or inspect.getmodule(cls) - globals = vars(mod) if mod else {} - owner = getattr(cls, "__qualname__", "").split(".")[0] - locals = globals.get(owner, {}) - return myTypeguard.Namespaces(globals, locals) - def _checkRecordAttr(cls: typing.Any, ns: myTypeguard.Namespaces, name: str, @@ -43,7 +36,6 @@ def _checkRecordAttr(cls: typing.Any, loc = location.Loc.fromFrameInfo(fi) else: loc = None - # FIXME: i18n raise errors.WyppTypeError.recordAssignError(cls.__name__, name, ty, @@ -52,7 +44,7 @@ def _checkRecordAttr(cls: typing.Any, loc) return v -def _patchDataClass(cls, mutable: bool): +def _patchDataClass(cls, mutable: bool, ns: myTypeguard.Namespaces): fieldNames = [f.name for f in dataclasses.fields(cls)] setattr(cls, EQ_ATTRS_ATTR, fieldNames) @@ -60,23 +52,25 @@ def _patchDataClass(cls, mutable: bool): # add annotions for type checked constructor. cls.__kind = 'record' cls.__init__.__annotations__ = _collectDataClassAttributes(cls) - cls.__init__ = typecheck.wrapTypecheckRecordConstructor(cls) + cls.__init__ = typecheck.wrapTypecheckRecordConstructor(cls, ns) if mutable: # prevent new fields being added fields = set(fieldNames) - ns = _getNamespacesOfClass(cls) info = location.RecordConstructorInfo(cls) - types = typing.get_type_hints(cls, include_extras=True) 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] @@ -88,11 +82,12 @@ def _setattr(obj, name, v): return cls @typing.dataclass_transform() -def record(cls=None, mutable=False): +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 _patchDataClass(newCls, mutable) + return utils._call_with_frames_removed(_patchDataClass, newCls, mutable, ns) else: return newCls # See if we're being called as @record or @record(). diff --git a/python/src/renderTy.py b/python/src/renderTy.py index db08bf1f..75807697 100644 --- a/python/src/renderTy.py +++ b/python/src/renderTy.py @@ -42,6 +42,8 @@ def renderTy(tp: Any) -> str: # 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}]" @@ -74,4 +76,7 @@ def renderTy(tp: Any) -> str: # Parametrized generics like list[T], dict[K, V], set[T], type[T], etc. name = getattr(origin, "__name__", None) or getattr(origin, "__qualname__", repr(origin)) - return f"{name}[" + ", ".join(renderTy(a) for a in args) + "]" + if args: + return f"{name}[" + ", ".join(renderTy(a) for a in args) + "]" + else: + return name diff --git a/python/src/runner.py b/python/src/runner.py index 7673e597..0b38af9a 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -306,12 +306,15 @@ def __enter__(self): if self.pathDir not in sys.path: sys.path.insert(0, self.pathDir) self.sysPathInserted = True - self.argv = self.args + 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 - self.argv = self.oldArgs + sys.argv = self.oldArgs def runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): if not extraDirs: @@ -442,6 +445,8 @@ def versionOk(v): else: return True +DEBUG = False + def main(globals, argList=None): v = sys.version_info if not versionOk(v): @@ -453,14 +458,14 @@ def main(globals, argList=None): sys.exit(1) (args, restArgs) = parseCmdlineArgs(argList) - global VERBOSE if args.verbose: - VERBOSE = True - global DEBUG + enableVerbose() if args.debug: + global DEBUG DEBUG = True + enableDebug() - verbose(f'VERBOSE={VERBOSE}, DEBUG={DEBUG}') + verbose(f'VERBOSE={args.verbose}, DEBUG={DEBUG}') if args.lang: if args.lang in i18n.allLanguages: diff --git a/python/src/stacktrace.py b/python/src/stacktrace.py index 15fe3625..630c06b4 100644 --- a/python/src/stacktrace.py +++ b/python/src/stacktrace.py @@ -27,8 +27,9 @@ def isWyppFrame(frame: types.FrameType): modName == 'typeguard' or modName.startswith('typeguard.') or \ modName == 'wypp' or modName.startswith('wypp.') -def isRunpyFrame(frame: types.FrameType): - return frame.f_code.co_filename == '' +def isRunpyFrame(frame: types.FrameType) -> bool: + f = frame.f_code.co_filename + return f == '' or f.startswith(' Optional[inspect.FrameInfo]: - if idx >= len(self.__returnFrames): + try: + f = self.__returnFrames[idx] + except IndexError: return None - f = self.__returnFrames[idx] if f: tb = inspect.getframeinfo(f, context=1) - return inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index) + fi = inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index) + del f + return fi else: return None -def installProfileHook(entriesToKeep: int) -> ReturnTracker: +# 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/src/typecheck.py b/python/src/typecheck.py index 4c905143..a5543d64 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -28,6 +28,7 @@ def isEmptySignature(sig: inspect.Signature) -> bool: def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool: match res: case MatchesTyFailure(exc, ty): + # raise exc raise errors.WyppTypeError.invalidType(ty, tyLoc) case b: return b @@ -76,44 +77,47 @@ def mandatoryArgCount(sig: inspect.Signature) -> int: 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: paramNames = list(sig.parameters) mandatory = mandatoryArgCount(sig) kind = getKind(cfg, paramNames) offset = 1 if kind == 'method' else 0 - cn = location.CallableName.mk(info) fi = stacktrace.callerOutsideWypp() def raiseArgMismatch(): callLoc = None if not fi else location.Loc.fromFrameInfo(fi) + cn = location.CallableName.mk(info) raise errors.WyppTypeError.argCountMismatch(cn, callLoc, len(paramNames) - offset, mandatory - offset, len(args) - offset) - if len(args) < mandatory: + 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] - t = p.annotation - if not isEmptyAnnotation(t): - a = args[i] - locDecl = info.getParamSourceLocation(name) - if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl): - if fi is not None: - locArg = location.locationOfArgument(fi, i) - else: - locArg = None - raise errors.WyppTypeError.argumentError(cn, - name, - i - offset, - locDecl, - t, - a, - locArg) + 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: + 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, code: location.CallableInfo, cfg: CheckCfg) -> None: @@ -146,37 +150,30 @@ def fromDict(d: dict) -> CheckCfg: kind = 'function' case 'method': kind = location.ClassMember('method', d['className']) - return CheckCfg(kind=kind, ns=Namespaces.empty()) - def setNamespaces(self, ns: Namespaces) -> CheckCfg: - return CheckCfg(kind=self.kind, ns=ns) + return CheckCfg(kind=kind, ns=Namespaces(d['globals'], d['locals'])) P = ParamSpec("P") T = TypeVar("T") -def getNamespacesOfCallable(func: Callable): - globals = func.__globals__ - # if it's a method, let it see the owning class namespace - owner = getattr(func, "__qualname__", "").split(".")[0] - locals = globals.get(owner, {}) - return Namespaces(globals, locals) - -def wrapTypecheck(cfg: dict, outerInfo: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]: - outerCheckCfg = CheckCfg.fromDict(cfg) +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 - checkCfg = outerCheckCfg.setNamespaces(getNamespacesOfCallable(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: - # when using _call_with_next_frame_removed, we have to take the second-to-last - # return. Hence, we keep the two most recent returns - returnTracker = stacktrace.installProfileHook(2) 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( @@ -186,11 +183,8 @@ def wrapped(*args, **kwargs) -> T: return wrapped return _wrap -def wrapTypecheckRecordConstructor(cls: type) -> Callable: - code = location.RecordConstructorInfo(cls) - return wrapTypecheck({'kind': 'method', 'className': cls.__name__}, code)(cls.__init__) - -def wrapNoTypecheck(cfg: dict) -> Callable[[Callable[P, T]], Callable[P, T]]: - def _wrap(f: Callable[P, T]) -> Callable[P, T]: - return f - 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/src/utils.py b/python/src/utils.py index 84cb309e..41a9d05a 100644 --- a/python/src/utils.py +++ b/python/src/utils.py @@ -34,6 +34,9 @@ def dropWhile(l: list, f: Callable[[Any], bool]) -> list: 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]: diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index d5532164..0240e72c 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -291,21 +291,15 @@ def deepEq(v1, v2, **flags): return False # v1 == v2 already checked return False -class TodoError(Exception, errors.WyppError): - pass - def todo(msg=None): if msg is None: msg = 'TODO' - raise TodoError(msg) - -class ImpossibleError(Exception, errors.WyppError): - 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 diff --git a/python/test-data-2.0/basics/async.err b/python/test-data-2.0/basics/async.err new file mode 100644 index 00000000..3db47e6d --- /dev/null +++ b/python/test-data-2.0/basics/async.err @@ -0,0 +1,25 @@ +Traceback (most recent call last): + File "async.py", line 10, in + asyncio.run(main()) + File "runners.py", line 194, in run + return runner.run(main) + File "runners.py", line 129, in run + signal.signal(signal.SIGINT, signal.default_int_handler) + File "base_events.py", line 684, in run_until_complete + return future.result() + File "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 async.py +## Fehlerhafter Aufruf in Zeile 7: + + x = await foo("blub") + +## Typ deklariert in Zeile 3: + +async def foo(i: int): diff --git a/python/test-data-2.0/basics/async.err_en b/python/test-data-2.0/basics/async.err_en new file mode 100644 index 00000000..5933f2b3 --- /dev/null +++ b/python/test-data-2.0/basics/async.err_en @@ -0,0 +1,25 @@ +Traceback (most recent call last): + File "async.py", line 10, in + asyncio.run(main()) + File "runners.py", line 194, in run + return runner.run(main) + File "runners.py", line 129, in run + signal.signal(signal.SIGINT, signal.default_int_handler) + File "base_events.py", line 684, in run_until_complete + return future.result() + File "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 async.py +## Problematic call in line 7: + + x = await foo("blub") + +## Type declared in line 3: + +async def foo(i: int): diff --git a/python/test-data/declared-at-missing.out b/python/test-data-2.0/basics/async.out similarity index 100% rename from python/test-data/declared-at-missing.out rename to python/test-data-2.0/basics/async.out diff --git a/python/test-data/modules/A/main.out b/python/test-data-2.0/basics/async.out_en similarity index 100% rename from python/test-data/modules/A/main.out rename to python/test-data-2.0/basics/async.out_en diff --git a/python/test-data-2.0/basics/async.py b/python/test-data-2.0/basics/async.py new file mode 100644 index 00000000..bcc83ba0 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/constructor.err b/python/test-data-2.0/basics/constructor.err index f9beaa9a..471fe111 100644 --- a/python/test-data-2.0/basics/constructor.err +++ b/python/test-data-2.0/basics/constructor.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 5, in + File "constructor.py", line 5, in c = C("1") WyppTypeError: "1" diff --git a/python/test-data-2.0/basics/constructor.err_en b/python/test-data-2.0/basics/constructor.err_en index 40012443..ed55f998 100644 --- a/python/test-data-2.0/basics/constructor.err_en +++ b/python/test-data-2.0/basics/constructor.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 5, in + File "constructor.py", line 5, in c = C("1") WyppTypeError: "1" diff --git a/python/test-data-2.0/basics/forwardRefs.err b/python/test-data-2.0/basics/forwardRefs.err index dd3063dc..0c5aa87b 100644 --- a/python/test-data-2.0/basics/forwardRefs.err +++ b/python/test-data-2.0/basics/forwardRefs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 12, in + File "forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> diff --git a/python/test-data-2.0/basics/forwardRefs.err_en b/python/test-data-2.0/basics/forwardRefs.err_en index dd6ad4e5..91cd288f 100644 --- a/python/test-data-2.0/basics/forwardRefs.err_en +++ b/python/test-data-2.0/basics/forwardRefs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 12, in + File "forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> diff --git a/python/test-data-2.0/basics/functionArg.err b/python/test-data-2.0/basics/functionArg.err index 1b87c548..6f4dd723 100644 --- a/python/test-data-2.0/basics/functionArg.err +++ b/python/test-data-2.0/basics/functionArg.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" diff --git a/python/test-data-2.0/basics/functionArg.err_en b/python/test-data-2.0/basics/functionArg.err_en index b6b52ae5..37416254 100644 --- a/python/test-data-2.0/basics/functionArg.err_en +++ b/python/test-data-2.0/basics/functionArg.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" diff --git a/python/test-data-2.0/basics/functionNoResult.err b/python/test-data-2.0/basics/functionNoResult.err index e8656f9d..3e5e7bf0 100644 --- a/python/test-data-2.0/basics/functionNoResult.err +++ b/python/test-data-2.0/basics/functionNoResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" diff --git a/python/test-data-2.0/basics/functionNoResult.err_en b/python/test-data-2.0/basics/functionNoResult.err_en index 923f6139..18983e58 100644 --- a/python/test-data-2.0/basics/functionNoResult.err_en +++ b/python/test-data-2.0/basics/functionNoResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" diff --git a/python/test-data-2.0/basics/functionNoResult2.err b/python/test-data-2.0/basics/functionNoResult2.err index 7546ff89..c8c6bb46 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err +++ b/python/test-data-2.0/basics/functionNoResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult2.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult2.py", line 2, in foo pass WyppTypeError: kein Rückgabewert vorhanden diff --git a/python/test-data-2.0/basics/functionNoResult2.err_en b/python/test-data-2.0/basics/functionNoResult2.err_en index 34095d71..5bfdd5c5 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err_en +++ b/python/test-data-2.0/basics/functionNoResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult2.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult2.py", line 2, in foo pass WyppTypeError: no result returned diff --git a/python/test-data-2.0/basics/functionNoResult3.err b/python/test-data-2.0/basics/functionNoResult3.err index db1dc09f..c78ac3b2 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err +++ b/python/test-data-2.0/basics/functionNoResult3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult3.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" diff --git a/python/test-data-2.0/basics/functionNoResult3.err_en b/python/test-data-2.0/basics/functionNoResult3.err_en index 90890d8c..accc5607 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err_en +++ b/python/test-data-2.0/basics/functionNoResult3.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 4, in + File "functionNoResult3.py", line 4, in foo(1) - File "", line 2, in foo + File "functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" diff --git a/python/test-data-2.0/basics/functionResult.err b/python/test-data-2.0/basics/functionResult.err index 3fd93c1a..15c578fa 100644 --- a/python/test-data-2.0/basics/functionResult.err +++ b/python/test-data-2.0/basics/functionResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 7, in + File "functionResult.py", line 7, in foo(1) - File "", line 5, in foo + File "functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" diff --git a/python/test-data-2.0/basics/functionResult.err_en b/python/test-data-2.0/basics/functionResult.err_en index 15e990e6..ee464109 100644 --- a/python/test-data-2.0/basics/functionResult.err_en +++ b/python/test-data-2.0/basics/functionResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 7, in + File "functionResult.py", line 7, in foo(1) - File "", line 5, in foo + File "functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" diff --git a/python/test-data-2.0/basics/functionResult2.err b/python/test-data-2.0/basics/functionResult2.err index a17bb953..be536d67 100644 --- a/python/test-data-2.0/basics/functionResult2.err +++ b/python/test-data-2.0/basics/functionResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 7, in + File "functionResult2.py", line 7, in foo(1) - File "", line 5, in foo + File "functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" diff --git a/python/test-data-2.0/basics/functionResult2.err_en b/python/test-data-2.0/basics/functionResult2.err_en index c1c439ce..766d5029 100644 --- a/python/test-data-2.0/basics/functionResult2.err_en +++ b/python/test-data-2.0/basics/functionResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 7, in + File "functionResult2.py", line 7, in foo(1) - File "", line 5, in foo + File "functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" diff --git a/python/test-data-2.0/basics/iterator.err b/python/test-data-2.0/basics/iterator.err index ac55b12b..196e9e46 100644 --- a/python/test-data-2.0/basics/iterator.err +++ b/python/test-data-2.0/basics/iterator.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 6, in + File "iterator.py", line 6, in g = my_generator() - File "", line 4, in my_generator + File "iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 diff --git a/python/test-data-2.0/basics/iterator.err_en b/python/test-data-2.0/basics/iterator.err_en index d0716ee0..77f9b078 100644 --- a/python/test-data-2.0/basics/iterator.err_en +++ b/python/test-data-2.0/basics/iterator.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 6, in + File "iterator.py", line 6, in g = my_generator() - File "", line 4, in my_generator + File "iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 diff --git a/python/test-data-2.0/basics/kwargs.err b/python/test-data-2.0/basics/kwargs.err new file mode 100644 index 00000000..b05b1527 --- /dev/null +++ b/python/test-data-2.0/basics/kwargs.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 kwargs.py +## Fehlerhafter Aufruf in Zeile 4: + +foo(1, y=1, z=2) + +## Typ deklariert in Zeile 1: + +def foo(x: int, y: int, z: str) -> int: diff --git a/python/test-data-2.0/basics/kwargs.err_en b/python/test-data-2.0/basics/kwargs.err_en new file mode 100644 index 00000000..99964074 --- /dev/null +++ b/python/test-data-2.0/basics/kwargs.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 kwargs.py +## Problematic call in line 4: + +foo(1, y=1, z=2) + +## Type declared in line 1: + +def foo(x: int, y: int, z: str) -> int: diff --git a/python/test-data/testABCMeta.out b/python/test-data-2.0/basics/kwargs.out similarity index 100% rename from python/test-data/testABCMeta.out rename to python/test-data-2.0/basics/kwargs.out diff --git a/python/test-data/testArgs.err b/python/test-data-2.0/basics/kwargs.out_en similarity index 100% rename from python/test-data/testArgs.err rename to python/test-data-2.0/basics/kwargs.out_en diff --git a/python/test-data-2.0/basics/kwargs.py b/python/test-data-2.0/basics/kwargs.py new file mode 100644 index 00000000..224f67ba --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/kwargs2.err b/python/test-data-2.0/basics/kwargs2.err new file mode 100644 index 00000000..64ad6240 --- /dev/null +++ b/python/test-data-2.0/basics/kwargs2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 kwargs2.py +## Fehlerhafter Aufruf in Zeile 4: + +foo(1, z=2, y='foo') + +## Typ deklariert in Zeile 1: + +def foo(x: int, y: int, z: str) -> int: diff --git a/python/test-data-2.0/basics/kwargs2.err_en b/python/test-data-2.0/basics/kwargs2.err_en new file mode 100644 index 00000000..911a68dd --- /dev/null +++ b/python/test-data-2.0/basics/kwargs2.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 kwargs2.py +## Problematic call in line 4: + +foo(1, z=2, y='foo') + +## Type declared in line 1: + +def foo(x: int, y: int, z: str) -> int: diff --git a/python/test-data/testCheck.err b/python/test-data-2.0/basics/kwargs2.out similarity index 100% rename from python/test-data/testCheck.err rename to python/test-data-2.0/basics/kwargs2.out diff --git a/python/test-data/testDeepEqBug.err b/python/test-data-2.0/basics/kwargs2.out_en similarity index 100% rename from python/test-data/testDeepEqBug.err rename to python/test-data-2.0/basics/kwargs2.out_en diff --git a/python/test-data-2.0/basics/kwargs2.py b/python/test-data-2.0/basics/kwargs2.py new file mode 100644 index 00000000..95eda6c6 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/kwargs_ok.err similarity index 100% rename from python/test-data/testForwardRef2.out rename to python/test-data-2.0/basics/kwargs_ok.err diff --git a/python/test-data-2.0/basics/kwargs_ok.out b/python/test-data-2.0/basics/kwargs_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/basics/kwargs_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/basics/kwargs_ok.py b/python/test-data-2.0/basics/kwargs_ok.py new file mode 100644 index 00000000..2851e3bc --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/listArg.err b/python/test-data-2.0/basics/listArg.err index 99748a6f..9e6500e0 100644 --- a/python/test-data-2.0/basics/listArg.err +++ b/python/test-data-2.0/basics/listArg.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "listArg.py", line 4, in foo([1, 2, "3"]) WyppTypeError: [1, 2, '3'] diff --git a/python/test-data-2.0/basics/listArg.err_en b/python/test-data-2.0/basics/listArg.err_en index 760497f3..f77bc492 100644 --- a/python/test-data-2.0/basics/listArg.err_en +++ b/python/test-data-2.0/basics/listArg.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "listArg.py", line 4, in foo([1, 2, "3"]) WyppTypeError: [1, 2, '3'] diff --git a/python/test-data-2.0/basics/listResult.err b/python/test-data-2.0/basics/listResult.err index 47f72a14..ba70d720 100644 --- a/python/test-data-2.0/basics/listResult.err +++ b/python/test-data-2.0/basics/listResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 5, in + File "listResult.py", line 5, in foo([1, 2, 3]) - File "", line 3, in foo + File "listResult.py", line 3, in foo return l WyppTypeError: [1, 2, 3, '4'] diff --git a/python/test-data-2.0/basics/listResult.err_en b/python/test-data-2.0/basics/listResult.err_en index 13f7a42a..36644de2 100644 --- a/python/test-data-2.0/basics/listResult.err_en +++ b/python/test-data-2.0/basics/listResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "", line 5, in + File "listResult.py", line 5, in foo([1, 2, 3]) - File "", line 3, in foo + File "listResult.py", line 3, in foo return l WyppTypeError: [1, 2, 3, '4'] diff --git a/python/test-data-2.0/basics/method.err b/python/test-data-2.0/basics/method.err index 632ce4b3..66b1b7c4 100644 --- a/python/test-data-2.0/basics/method.err +++ b/python/test-data-2.0/basics/method.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "method.py", line 9, in c.method("2") WyppTypeError: "2" diff --git a/python/test-data-2.0/basics/method.err_en b/python/test-data-2.0/basics/method.err_en index 88563a4f..9c391898 100644 --- a/python/test-data-2.0/basics/method.err_en +++ b/python/test-data-2.0/basics/method.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "method.py", line 9, in c.method("2") WyppTypeError: "2" diff --git a/python/test-data-2.0/basics/mutable.err b/python/test-data-2.0/basics/mutable.err index 40ec7c04..3034ff86 100644 --- a/python/test-data-2.0/basics/mutable.err +++ b/python/test-data-2.0/basics/mutable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 10, in + File "mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" diff --git a/python/test-data-2.0/basics/mutable.err_en b/python/test-data-2.0/basics/mutable.err_en index 840729b6..6ed27a1d 100644 --- a/python/test-data-2.0/basics/mutable.err_en +++ b/python/test-data-2.0/basics/mutable.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 10, in + File "mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" diff --git a/python/test-data-2.0/basics/mutable2.err b/python/test-data-2.0/basics/mutable2.err index 12014cde..15d4622b 100644 --- a/python/test-data-2.0/basics/mutable2.err +++ b/python/test-data-2.0/basics/mutable2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' diff --git a/python/test-data-2.0/basics/mutable2.err_en b/python/test-data-2.0/basics/mutable2.err_en index fa5df34e..93ef603a 100644 --- a/python/test-data-2.0/basics/mutable2.err_en +++ b/python/test-data-2.0/basics/mutable2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' diff --git a/python/test-data-2.0/basics/nested.err b/python/test-data-2.0/basics/nested.err index bc736773..732455ab 100644 --- a/python/test-data-2.0/basics/nested.err +++ b/python/test-data-2.0/basics/nested.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "nested.py", line 4, in foo(42) WyppTypeError: 42 diff --git a/python/test-data-2.0/basics/nestedFun.err b/python/test-data-2.0/basics/nestedFun.err new file mode 100644 index 00000000..1bd7d166 --- /dev/null +++ b/python/test-data-2.0/basics/nestedFun.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "nestedFun.py", line 9, in + foo(1) + File "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 nestedFun.py +## Fehlerhafter Aufruf in Zeile 7: + + return bar("foo") + +## Typ deklariert in Zeile 5: + + def bar(j: int) -> int: diff --git a/python/test-data-2.0/basics/nestedFun.err_en b/python/test-data-2.0/basics/nestedFun.err_en new file mode 100644 index 00000000..11d5323b --- /dev/null +++ b/python/test-data-2.0/basics/nestedFun.err_en @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "nestedFun.py", line 9, in + foo(1) + File "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 nestedFun.py +## Problematic call in line 7: + + return bar("foo") + +## Type declared in line 5: + + def bar(j: int) -> int: diff --git a/python/test-data/testForwardRef4.out b/python/test-data-2.0/basics/nestedFun.out similarity index 100% rename from python/test-data/testForwardRef4.out rename to python/test-data-2.0/basics/nestedFun.out diff --git a/python/test-data/testForwardRef5.out b/python/test-data-2.0/basics/nestedFun.out_en similarity index 100% rename from python/test-data/testForwardRef5.out rename to python/test-data-2.0/basics/nestedFun.out_en diff --git a/python/test-data-2.0/basics/nestedFun.py b/python/test-data-2.0/basics/nestedFun.py new file mode 100644 index 00000000..0cd20063 --- /dev/null +++ b/python/test-data-2.0/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-2.0/basics/optionalArgs.err b/python/test-data-2.0/basics/optionalArgs.err index a3dd5ede..b40f2c3f 100644 --- a/python/test-data-2.0/basics/optionalArgs.err +++ b/python/test-data-2.0/basics/optionalArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 1, in + File "optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 diff --git a/python/test-data-2.0/basics/optionalArgs.err_en b/python/test-data-2.0/basics/optionalArgs.err_en index 7939ab76..51577da9 100644 --- a/python/test-data-2.0/basics/optionalArgs.err_en +++ b/python/test-data-2.0/basics/optionalArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 1, in + File "optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 diff --git a/python/test-data-2.0/basics/optionalArgs2.err b/python/test-data-2.0/basics/optionalArgs2.err index 5fd81cf1..3351d664 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err +++ b/python/test-data-2.0/basics/optionalArgs2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "optionalArgs2.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/optionalArgs2.err_en b/python/test-data-2.0/basics/optionalArgs2.err_en index 730f78d5..083811a2 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err_en +++ b/python/test-data-2.0/basics/optionalArgs2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "optionalArgs2.py", line 4, in foo(1) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/basics/optionalArgs3.err b/python/test-data-2.0/basics/optionalArgs3.err index 22782f9e..d3660853 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err +++ b/python/test-data-2.0/basics/optionalArgs3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/optionalArgs3.err_en b/python/test-data-2.0/basics/optionalArgs3.err_en index f2756f69..6f82f25d 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err_en +++ b/python/test-data-2.0/basics/optionalArgs3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/basics/optionalArgs4.err b/python/test-data-2.0/basics/optionalArgs4.err index bdcd6df7..7dd68ed7 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err +++ b/python/test-data-2.0/basics/optionalArgs4.err @@ -1,8 +1,8 @@ Traceback (most recent call last): - File "", line 1, in - class C: - File "", line 1, in C + File "optionalArgs4.py", line 1, in class C: + File "optionalArgs4.py", line 2, in C + def __init__(i: int, s: str=2): WyppTypeError: 2 diff --git a/python/test-data-2.0/basics/optionalArgs4.err_en b/python/test-data-2.0/basics/optionalArgs4.err_en index 0b3d8128..1c073588 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err_en +++ b/python/test-data-2.0/basics/optionalArgs4.err_en @@ -1,8 +1,8 @@ Traceback (most recent call last): - File "", line 1, in - class C: - File "", line 1, in C + File "optionalArgs4.py", line 1, in class C: + File "optionalArgs4.py", line 2, in C + def __init__(i: int, s: str=2): WyppTypeError: 2 diff --git a/python/test-data-2.0/basics/optionalAttr.err b/python/test-data-2.0/basics/optionalAttr.err index 0b11ee05..8f4bfa83 100644 --- a/python/test-data-2.0/basics/optionalAttr.err +++ b/python/test-data-2.0/basics/optionalAttr.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 3, in + File "optionalAttr.py", line 3, in @record WyppTypeError: 'foo' diff --git a/python/test-data-2.0/basics/optionalAttr.err_en b/python/test-data-2.0/basics/optionalAttr.err_en index 0c545844..a8b6f0d0 100644 --- a/python/test-data-2.0/basics/optionalAttr.err_en +++ b/python/test-data-2.0/basics/optionalAttr.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 3, in + File "optionalAttr.py", line 3, in @record WyppTypeError: 'foo' diff --git a/python/test-data-2.0/basics/optionalAttr2.err b/python/test-data-2.0/basics/optionalAttr2.err index ce675bec..4983d25e 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err +++ b/python/test-data-2.0/basics/optionalAttr2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "optionalAttr2.py", line 9, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/optionalAttr2.err_en b/python/test-data-2.0/basics/optionalAttr2.err_en index 79ec47df..706c936c 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err_en +++ b/python/test-data-2.0/basics/optionalAttr2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "optionalAttr2.py", line 9, in c = C(1) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/basics/optionalAttr3.err b/python/test-data-2.0/basics/optionalAttr3.err index 9890de0a..c22ad0d0 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err +++ b/python/test-data-2.0/basics/optionalAttr3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/optionalAttr3.err_en b/python/test-data-2.0/basics/optionalAttr3.err_en index bcb14c41..1c9d97bd 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err_en +++ b/python/test-data-2.0/basics/optionalAttr3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 9, in + File "optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/basics/partial.err b/python/test-data-2.0/basics/partial.err index 63bc45c5..c0a4c30e 100644 --- a/python/test-data-2.0/basics/partial.err +++ b/python/test-data-2.0/basics/partial.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 1, in + File "partial.py", line 1, in def foo(i: int, j) -> None: WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typannotation. diff --git a/python/test-data-2.0/basics/partial.err_en b/python/test-data-2.0/basics/partial.err_en index a94d83c8..4771107c 100644 --- a/python/test-data-2.0/basics/partial.err_en +++ b/python/test-data-2.0/basics/partial.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 1, in + File "partial.py", line 1, in def foo(i: int, j) -> None: WyppTypeError: Parameter `j` of function `foo` requires a type annotation. diff --git a/python/test-data-2.0/basics/record.err b/python/test-data-2.0/basics/record.err index 14556db8..591d41ef 100644 --- a/python/test-data-2.0/basics/record.err +++ b/python/test-data-2.0/basics/record.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" diff --git a/python/test-data-2.0/basics/record.err_en b/python/test-data-2.0/basics/record.err_en index e0b1ad06..102e3bf7 100644 --- a/python/test-data-2.0/basics/record.err_en +++ b/python/test-data-2.0/basics/record.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" diff --git a/python/test-data/testFunEq.err b/python/test-data-2.0/basics/sample_ok.err similarity index 100% rename from python/test-data/testFunEq.err rename to python/test-data-2.0/basics/sample_ok.err diff --git a/python/test-data-2.0/basics/sample_ok.out b/python/test-data-2.0/basics/sample_ok.out new file mode 100644 index 00000000..aaf9a472 --- /dev/null +++ b/python/test-data-2.0/basics/sample_ok.out @@ -0,0 +1 @@ +14 Tests, alle erfolgreich 😀 diff --git a/python/test-data-2.0/basics/sample_ok.py b/python/test-data-2.0/basics/sample_ok.py new file mode 100644 index 00000000..d117e9fb --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/stack.err b/python/test-data-2.0/basics/stack.err index af02db0c..69ecbdac 100644 --- a/python/test-data-2.0/basics/stack.err +++ b/python/test-data-2.0/basics/stack.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "", line 7, in + File "stack.py", line 7, in factorial(5) - File "", line 5, in factorial + File "stack.py", line 5, in factorial return factorial(n - 1) * n - File "", line 5, in factorial + File "stack.py", line 5, in factorial return factorial(n - 1) * n - File "", line 5, in factorial + File "stack.py", line 5, in factorial return factorial(n - 1) * n [Previous line repeated 2 more times] - File "", line 3, in factorial + File "stack.py", line 3, in factorial raise ValueError('kein Bock') ValueError: kein Bock diff --git a/python/test-data-2.0/basics/staticmethod.err b/python/test-data-2.0/basics/staticmethod.err index a7f862ce..b50b4a04 100644 --- a/python/test-data-2.0/basics/staticmethod.err +++ b/python/test-data-2.0/basics/staticmethod.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 6, in + File "staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" diff --git a/python/test-data-2.0/basics/staticmethod.err_en b/python/test-data-2.0/basics/staticmethod.err_en index 3690c706..b6594829 100644 --- a/python/test-data-2.0/basics/staticmethod.err_en +++ b/python/test-data-2.0/basics/staticmethod.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 6, in + File "staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" diff --git a/python/test-data-2.0/basics/testCallable.err b/python/test-data-2.0/basics/testCallable.err index ef6b5a1d..579ebbaf 100644 --- a/python/test-data-2.0/basics/testCallable.err +++ b/python/test-data-2.0/basics/testCallable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 6, in + File "testCallable.py", line 6, in foo(42) WyppTypeError: 42 diff --git a/python/test-data-2.0/basics/tooFewArgs.err b/python/test-data-2.0/basics/tooFewArgs.err index 2a02cacd..37ef66f8 100644 --- a/python/test-data-2.0/basics/tooFewArgs.err +++ b/python/test-data-2.0/basics/tooFewArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "tooFewArgs.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/tooFewAttrs.err b/python/test-data-2.0/basics/tooFewAttrs.err index 004ca134..7012682e 100644 --- a/python/test-data-2.0/basics/tooFewAttrs.err +++ b/python/test-data-2.0/basics/tooFewAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "tooFewAttrs.py", line 8, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/tooManyArgs.err b/python/test-data-2.0/basics/tooManyArgs.err index 0419dcdb..e8358624 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err +++ b/python/test-data-2.0/basics/tooManyArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/tooManyArgs.err_en b/python/test-data-2.0/basics/tooManyArgs.err_en index 4582c4ea..2fcd8b43 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err_en +++ b/python/test-data-2.0/basics/tooManyArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/basics/tooManyAttrs.err b/python/test-data-2.0/basics/tooManyAttrs.err index 28cf37c1..47ee237c 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err +++ b/python/test-data-2.0/basics/tooManyAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht diff --git a/python/test-data-2.0/basics/tooManyAttrs.err_en b/python/test-data-2.0/basics/tooManyAttrs.err_en index 39680530..7176e373 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err_en +++ b/python/test-data-2.0/basics/tooManyAttrs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 8, in + File "tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: argument count mismatch diff --git a/python/test-data-2.0/extras/testTypes2.err b/python/test-data-2.0/extras/testTypes2.err index 6f3b6857..ea3b0a81 100644 --- a/python/test-data-2.0/extras/testTypes2.err +++ b/python/test-data-2.0/extras/testTypes2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "", line 4, in + File "testTypes2.py", line 4, in inc("1") WyppTypeError: "1" diff --git a/python/test-data/declared-at-missing.err b/python/test-data-2.0/failing/declared-at-missing.err similarity index 90% rename from python/test-data/declared-at-missing.err rename to python/test-data-2.0/failing/declared-at-missing.err index 937cd8db..ee8a9cb1 100644 --- a/python/test-data/declared-at-missing.err +++ b/python/test-data-2.0/failing/declared-at-missing.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/declared-at-missing.py", line 22, in + File "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=()) diff --git a/python/test-data/declared-at-missing.err-3.10 b/python/test-data-2.0/failing/declared-at-missing.err-3.10 similarity index 100% rename from python/test-data/declared-at-missing.err-3.10 rename to python/test-data-2.0/failing/declared-at-missing.err-3.10 diff --git a/python/test-data/declared-at-missing.err-3.11 b/python/test-data-2.0/failing/declared-at-missing.err-3.11 similarity index 100% rename from python/test-data/declared-at-missing.err-3.11 rename to python/test-data-2.0/failing/declared-at-missing.err-3.11 diff --git a/python/test-data-2.0/failing/declared-at-missing.err-3.12 b/python/test-data-2.0/failing/declared-at-missing.err-3.12 new file mode 100644 index 00000000..5d7e02f9 --- /dev/null +++ b/python/test-data-2.0/failing/declared-at-missing.err-3.12 @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "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 declared-at-missing.py +## Fehlerhafter Aufruf in Zeile 22: + +semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) + +## Typ deklariert in Zeile 19: + + courses: tuple[CourseM, ...] diff --git a/python/test-data/testGetSource.out b/python/test-data-2.0/failing/declared-at-missing.out similarity index 100% rename from python/test-data/testGetSource.out rename to python/test-data-2.0/failing/declared-at-missing.out diff --git a/python/test-data/declared-at-missing.py b/python/test-data-2.0/failing/declared-at-missing.py similarity index 100% rename from python/test-data/declared-at-missing.py rename to python/test-data-2.0/failing/declared-at-missing.py diff --git a/python/test-data/modules/A/main.err b/python/test-data-2.0/failing/main.err similarity index 84% rename from python/test-data/modules/A/main.err rename to python/test-data-2.0/failing/main.err index 0060cc41..1829924c 100644 --- a/python/test-data/modules/A/main.err +++ b/python/test-data-2.0/failing/main.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/modules/A/main.py", line 3, in + File "modules/A/main.py", line 3, in mod.foo(1) File "", line 5, in foo return bar(i) diff --git a/python/test-data/testHintParentheses1.out b/python/test-data-2.0/failing/main.out similarity index 100% rename from python/test-data/testHintParentheses1.out rename to python/test-data-2.0/failing/main.out diff --git a/python/test-data/modules/A/main.py b/python/test-data-2.0/failing/main.py similarity index 100% rename from python/test-data/modules/A/main.py rename to python/test-data-2.0/failing/main.py diff --git a/python/test-data/testABCMeta.err b/python/test-data-2.0/failing/testABCMeta.err similarity index 74% rename from python/test-data/testABCMeta.err rename to python/test-data-2.0/failing/testABCMeta.err index 2bba158d..b58a7a1f 100644 --- a/python/test-data/testABCMeta.err +++ b/python/test-data-2.0/failing/testABCMeta.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data/testABCMeta.py", line 28, in + File "testABCMeta.py", line 28, in Circle(Point(0, 0), 1) TypeError: Can't instantiate abstract class Circle without an implementation for abstract method 'area' diff --git a/python/test-data/testABCMeta.err-3.10 b/python/test-data-2.0/failing/testABCMeta.err-3.10 similarity index 100% rename from python/test-data/testABCMeta.err-3.10 rename to python/test-data-2.0/failing/testABCMeta.err-3.10 diff --git a/python/test-data/testABCMeta.err-3.11 b/python/test-data-2.0/failing/testABCMeta.err-3.11 similarity index 100% rename from python/test-data/testABCMeta.err-3.11 rename to python/test-data-2.0/failing/testABCMeta.err-3.11 diff --git a/python/test-data/testHintParentheses2.out b/python/test-data-2.0/failing/testABCMeta.out similarity index 100% rename from python/test-data/testHintParentheses2.out rename to python/test-data-2.0/failing/testABCMeta.out diff --git a/python/test-data/testABCMeta.py b/python/test-data-2.0/failing/testABCMeta.py similarity index 100% rename from python/test-data/testABCMeta.py rename to python/test-data-2.0/failing/testABCMeta.py diff --git a/python/test-data/testHintParentheses3.out b/python/test-data-2.0/failing/testArgs_ok.err similarity index 100% rename from python/test-data/testHintParentheses3.out rename to python/test-data-2.0/failing/testArgs_ok.err diff --git a/python/test-data-2.0/failing/testArgs_ok.out b/python/test-data-2.0/failing/testArgs_ok.out new file mode 100644 index 00000000..c907ab7c --- /dev/null +++ b/python/test-data-2.0/failing/testArgs_ok.out @@ -0,0 +1 @@ +['test-data-2.0/failing/testArgs_ok.py', 'ARG_1', 'ARG_2'] diff --git a/python/test-data-2.0/failing/testArgs_ok.py b/python/test-data-2.0/failing/testArgs_ok.py new file mode 100644 index 00000000..1b88936f --- /dev/null +++ b/python/test-data-2.0/failing/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/testImpossible.out b/python/test-data-2.0/failing/testCheck_ok.err similarity index 100% rename from python/test-data/testImpossible.out rename to python/test-data-2.0/failing/testCheck_ok.err diff --git a/python/test-data-2.0/failing/testCheck_ok.out b/python/test-data-2.0/failing/testCheck_ok.out new file mode 100644 index 00000000..2c6e1965 --- /dev/null +++ b/python/test-data-2.0/failing/testCheck_ok.out @@ -0,0 +1,5 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.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/testCheck.py b/python/test-data-2.0/failing/testCheck_ok.py similarity index 100% rename from python/test-data/testCheck.py rename to python/test-data-2.0/failing/testCheck_ok.py diff --git a/python/test-data/testIndexError.out b/python/test-data-2.0/failing/testDeepEqBug_ok.err similarity index 100% rename from python/test-data/testIndexError.out rename to python/test-data-2.0/failing/testDeepEqBug_ok.err diff --git a/python/test-data-2.0/failing/testDeepEqBug_ok.out b/python/test-data-2.0/failing/testDeepEqBug_ok.out new file mode 100644 index 00000000..d525fb31 --- /dev/null +++ b/python/test-data-2.0/failing/testDeepEqBug_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testDeepEqBug_ok.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/testDeepEqBug.py b/python/test-data-2.0/failing/testDeepEqBug_ok.py similarity index 100% rename from python/test-data/testDeepEqBug.py rename to python/test-data-2.0/failing/testDeepEqBug_ok.py diff --git a/python/test-data-2.0/failing/testForwardRef2.err b/python/test-data-2.0/failing/testForwardRef2.err new file mode 100644 index 00000000..b155adf6 --- /dev/null +++ b/python/test-data-2.0/failing/testForwardRef2.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "testForwardRef2.py", line 10, in + t = Test(FooX()) + +WyppTypeError: ungültiger Typ `Foo` + +## Datei testForwardRef2.py +## Typ deklariert in Zeile 2: + + def __init__(self, foo: 'Foo'): diff --git a/python/test-data/testInvalidLiteral.out b/python/test-data-2.0/failing/testForwardRef2.out similarity index 100% rename from python/test-data/testInvalidLiteral.out rename to python/test-data-2.0/failing/testForwardRef2.out diff --git a/python/test-data/testForwardRef2.py b/python/test-data-2.0/failing/testForwardRef2.py similarity index 100% rename from python/test-data/testForwardRef2.py rename to python/test-data-2.0/failing/testForwardRef2.py diff --git a/python/test-data/testForwardRef4.err b/python/test-data-2.0/failing/testForwardRef4.err similarity index 77% rename from python/test-data/testForwardRef4.err rename to python/test-data-2.0/failing/testForwardRef4.err index 261721fc..3230ad3b 100644 --- a/python/test-data/testForwardRef4.err +++ b/python/test-data-2.0/failing/testForwardRef4.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testForwardRef4.py", line 11, in + File "testForwardRef4.py", line 11, in t = Test(Foo()) WyppNameError: name 'FooX' is not defined. Type annotation inside of class 'Test' could not be resolved. diff --git a/python/test-data/testForwardRef4.err-3.10 b/python/test-data-2.0/failing/testForwardRef4.err-3.10 similarity index 100% rename from python/test-data/testForwardRef4.err-3.10 rename to python/test-data-2.0/failing/testForwardRef4.err-3.10 diff --git a/python/test-data/testForwardRef4.err-3.11 b/python/test-data-2.0/failing/testForwardRef4.err-3.11 similarity index 100% rename from python/test-data/testForwardRef4.err-3.11 rename to python/test-data-2.0/failing/testForwardRef4.err-3.11 diff --git a/python/test-data-2.0/failing/testForwardRef4.err-3.12 b/python/test-data-2.0/failing/testForwardRef4.err-3.12 new file mode 100644 index 00000000..610aa3f5 --- /dev/null +++ b/python/test-data-2.0/failing/testForwardRef4.err-3.12 @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "testForwardRef4.py", line 11, in + t = Test(Foo()) + +WyppTypeError: ungültiger Typ `FooX` + +## Datei testForwardRef4.py +## Typ deklariert in Zeile 5: + + foo: 'FooX' diff --git a/python/test-data/testLiteral1.out b/python/test-data-2.0/failing/testForwardRef4.out similarity index 100% rename from python/test-data/testLiteral1.out rename to python/test-data-2.0/failing/testForwardRef4.out diff --git a/python/test-data/testForwardRef4.py b/python/test-data-2.0/failing/testForwardRef4.py similarity index 100% rename from python/test-data/testForwardRef4.py rename to python/test-data-2.0/failing/testForwardRef4.py diff --git a/python/test-data/testForwardRef5.err b/python/test-data-2.0/failing/testForwardRef5.err similarity index 87% rename from python/test-data/testForwardRef5.err rename to python/test-data-2.0/failing/testForwardRef5.err index 67974d66..8e1e5127 100644 --- a/python/test-data/testForwardRef5.err +++ b/python/test-data-2.0/failing/testForwardRef5.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testForwardRef5.py", line 23, in + File "testForwardRef5.py", line 23, in for car in garage.cars: WyppTypeError: list is not a list[Car] given: [Car(color='red'), 'Not A Car'] diff --git a/python/test-data/testForwardRef5.err-3.10 b/python/test-data-2.0/failing/testForwardRef5.err-3.10 similarity index 100% rename from python/test-data/testForwardRef5.err-3.10 rename to python/test-data-2.0/failing/testForwardRef5.err-3.10 diff --git a/python/test-data/testForwardRef5.err-3.11 b/python/test-data-2.0/failing/testForwardRef5.err-3.11 similarity index 100% rename from python/test-data/testForwardRef5.err-3.11 rename to python/test-data-2.0/failing/testForwardRef5.err-3.11 diff --git a/python/test-data-2.0/failing/testForwardRef5.err-3.12 b/python/test-data-2.0/failing/testForwardRef5.err-3.12 new file mode 100644 index 00000000..7a625d40 --- /dev/null +++ b/python/test-data-2.0/failing/testForwardRef5.err-3.12 @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "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 testForwardRef5.py +## Fehlerhafter Aufruf in Zeile 22: + +garage = Garage(cars=[Car(color='red'), "Not A Car"]) + +## Typ deklariert in Zeile 11: + + cars: list['Car'] diff --git a/python/test-data/testLockFactory.out b/python/test-data-2.0/failing/testForwardRef5.out similarity index 100% rename from python/test-data/testLockFactory.out rename to python/test-data-2.0/failing/testForwardRef5.out diff --git a/python/test-data/testForwardRef5.py b/python/test-data-2.0/failing/testForwardRef5.py similarity index 100% rename from python/test-data/testForwardRef5.py rename to python/test-data-2.0/failing/testForwardRef5.py diff --git a/python/test-data/testLockFactory2.out b/python/test-data-2.0/failing/testFunEq_ok.err similarity index 100% rename from python/test-data/testLockFactory2.out rename to python/test-data-2.0/failing/testFunEq_ok.err diff --git a/python/test-data-2.0/failing/testFunEq_ok.out b/python/test-data-2.0/failing/testFunEq_ok.out new file mode 100644 index 00000000..787f6567 --- /dev/null +++ b/python/test-data-2.0/failing/testFunEq_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist +1 Test, 1 Fehler 🙁 diff --git a/python/test-data/testFunEq.py b/python/test-data-2.0/failing/testFunEq_ok.py similarity index 100% rename from python/test-data/testFunEq.py rename to python/test-data-2.0/failing/testFunEq_ok.py diff --git a/python/test-data/testGetSource.err b/python/test-data-2.0/failing/testGetSource.err similarity index 77% rename from python/test-data/testGetSource.err rename to python/test-data-2.0/failing/testGetSource.err index 4e07caab..4e0a559f 100644 --- a/python/test-data/testGetSource.err +++ b/python/test-data-2.0/failing/testGetSource.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data/testGetSource.py", line 11, in + File "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/testMissingReturn.out b/python/test-data-2.0/failing/testGetSource.out similarity index 100% rename from python/test-data/testMissingReturn.out rename to python/test-data-2.0/failing/testGetSource.out diff --git a/python/test-data/testGetSource.py b/python/test-data-2.0/failing/testGetSource.py similarity index 100% rename from python/test-data/testGetSource.py rename to python/test-data-2.0/failing/testGetSource.py diff --git a/python/test-data-2.0/failing/testHintParentheses1.err b/python/test-data-2.0/failing/testHintParentheses1.err new file mode 100644 index 00000000..d5922767 --- /dev/null +++ b/python/test-data-2.0/failing/testHintParentheses1.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "testHintParentheses1.py", line 8, in + check(foo([1,2,3]), 3) + +WyppTypeError: ungültiger Typ `list(int)` + +## Datei testHintParentheses1.py +## Typ deklariert in Zeile 5: + +def foo(l: list(int)) -> int: diff --git a/python/test-data/testOriginalTypeNames.err b/python/test-data-2.0/failing/testHintParentheses1.out similarity index 100% rename from python/test-data/testOriginalTypeNames.err rename to python/test-data-2.0/failing/testHintParentheses1.out diff --git a/python/test-data/testHintParentheses1.py b/python/test-data-2.0/failing/testHintParentheses1.py similarity index 100% rename from python/test-data/testHintParentheses1.py rename to python/test-data-2.0/failing/testHintParentheses1.py diff --git a/python/test-data-2.0/failing/testHintParentheses2.err b/python/test-data-2.0/failing/testHintParentheses2.err new file mode 100644 index 00000000..7140ff7d --- /dev/null +++ b/python/test-data-2.0/failing/testHintParentheses2.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "testHintParentheses2.py", line 8, in + foo(1, {}) + +WyppTypeError: ungültiger Typ `dict[1, list(int)]` + +## Datei testHintParentheses2.py +## Typ deklariert in Zeile 5: + +def foo(a: 'int', b: 'dict[1, list(int)]') -> int: diff --git a/python/test-data/testRecordSetTypeForwardRef.out b/python/test-data-2.0/failing/testHintParentheses2.out similarity index 100% rename from python/test-data/testRecordSetTypeForwardRef.out rename to python/test-data-2.0/failing/testHintParentheses2.out diff --git a/python/test-data/testHintParentheses2.py b/python/test-data-2.0/failing/testHintParentheses2.py similarity index 100% rename from python/test-data/testHintParentheses2.py rename to python/test-data-2.0/failing/testHintParentheses2.py diff --git a/python/test-data/testHintParentheses3.err b/python/test-data-2.0/failing/testHintParentheses3.err similarity index 86% rename from python/test-data/testHintParentheses3.err rename to python/test-data-2.0/failing/testHintParentheses3.err index 5ac7b6ba..48b0e2b2 100644 --- a/python/test-data/testHintParentheses3.err +++ b/python/test-data-2.0/failing/testHintParentheses3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testHintParentheses3.py", line 6, in + File "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]? diff --git a/python/test-data/testRecordSetTypes.out b/python/test-data-2.0/failing/testHintParentheses3.out similarity index 100% rename from python/test-data/testRecordSetTypes.out rename to python/test-data-2.0/failing/testHintParentheses3.out diff --git a/python/test-data/testHintParentheses3.py b/python/test-data-2.0/failing/testHintParentheses3.py similarity index 100% rename from python/test-data/testHintParentheses3.py rename to python/test-data-2.0/failing/testHintParentheses3.py diff --git a/python/test-data-2.0/failing/testImpossible.err b/python/test-data-2.0/failing/testImpossible.err new file mode 100644 index 00000000..e41ab759 --- /dev/null +++ b/python/test-data-2.0/failing/testImpossible.err @@ -0,0 +1,7 @@ +Traceback (most recent call last): + File "testImpossible.py", line 3, in + impossible() + File "writeYourProgram.py", line 302, in impossible + raise errors.ImpossibleError(msg) + +Das Unmögliche ist passiert! diff --git a/python/test-data/testRecordTypes.out b/python/test-data-2.0/failing/testImpossible.out similarity index 100% rename from python/test-data/testRecordTypes.out rename to python/test-data-2.0/failing/testImpossible.out diff --git a/python/test-data/testImpossible.py b/python/test-data-2.0/failing/testImpossible.py similarity index 100% rename from python/test-data/testImpossible.py rename to python/test-data-2.0/failing/testImpossible.py diff --git a/python/test-data-2.0/failing/testIndexError.err b/python/test-data-2.0/failing/testIndexError.err new file mode 100644 index 00000000..548499a2 --- /dev/null +++ b/python/test-data-2.0/failing/testIndexError.err @@ -0,0 +1,6 @@ +Traceback (most recent call last): + File "testIndexError.py", line 6, in + foo([1,2,3]) + File "testIndexError.py", line 3, in foo + x = l[42] +IndexError: list index out of range diff --git a/python/test-data/testTodo.out b/python/test-data-2.0/failing/testIndexError.out similarity index 100% rename from python/test-data/testTodo.out rename to python/test-data-2.0/failing/testIndexError.out diff --git a/python/test-data/testIndexError.py b/python/test-data-2.0/failing/testIndexError.py similarity index 100% rename from python/test-data/testIndexError.py rename to python/test-data-2.0/failing/testIndexError.py diff --git a/python/test-data-2.0/failing/testInvalidLiteral.err b/python/test-data-2.0/failing/testInvalidLiteral.err new file mode 100644 index 00000000..de63bb47 --- /dev/null +++ b/python/test-data-2.0/failing/testInvalidLiteral.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "testInvalidLiteral.py", line 8, in + gameFull([['x']]) + +WyppTypeError: ungültiger Typ `list[list[['x', 'o', '-']]]` + +## Datei testInvalidLiteral.py +## Typ deklariert in Zeile 5: + +def gameFull(game:Game)->bool: diff --git a/python/test-data/testTraceback2.out b/python/test-data-2.0/failing/testInvalidLiteral.out similarity index 100% rename from python/test-data/testTraceback2.out rename to python/test-data-2.0/failing/testInvalidLiteral.out diff --git a/python/test-data/testInvalidLiteral.py b/python/test-data-2.0/failing/testInvalidLiteral.py similarity index 100% rename from python/test-data/testInvalidLiteral.py rename to python/test-data-2.0/failing/testInvalidLiteral.py diff --git a/python/test-data/testTraceback3.out b/python/test-data-2.0/failing/testIterable7_ok.err similarity index 100% rename from python/test-data/testTraceback3.out rename to python/test-data-2.0/failing/testIterable7_ok.err diff --git a/python/test-data-2.0/failing/testIterable7_ok.out b/python/test-data-2.0/failing/testIterable7_ok.out new file mode 100644 index 00000000..db562fb0 --- /dev/null +++ b/python/test-data-2.0/failing/testIterable7_ok.out @@ -0,0 +1,3 @@ +start of foo +end of foo +[15] diff --git a/python/test-data/testIterable7.py b/python/test-data-2.0/failing/testIterable7_ok.py similarity index 100% rename from python/test-data/testIterable7.py rename to python/test-data-2.0/failing/testIterable7_ok.py diff --git a/python/test-data-2.0/failing/testIterableImplicitAny.err b/python/test-data-2.0/failing/testIterableImplicitAny.err new file mode 100644 index 00000000..5b7fd00c --- /dev/null +++ b/python/test-data-2.0/failing/testIterableImplicitAny.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testIterableImplicitAny.py +## Fehlerhafter Aufruf in Zeile 13: + +foo(NotIterable()) + +## Typ deklariert in Zeile 7: + +def foo(it: Iterable) -> int: diff --git a/python/test-data/testIterableImplicitAny.out b/python/test-data-2.0/failing/testIterableImplicitAny.out similarity index 100% rename from python/test-data/testIterableImplicitAny.out rename to python/test-data-2.0/failing/testIterableImplicitAny.out diff --git a/python/test-data/testIterableImplicitAny.py b/python/test-data-2.0/failing/testIterableImplicitAny.py similarity index 100% rename from python/test-data/testIterableImplicitAny.py rename to python/test-data-2.0/failing/testIterableImplicitAny.py diff --git a/python/test-data/testLiteral1.err b/python/test-data-2.0/failing/testLiteral1.err similarity index 73% rename from python/test-data/testLiteral1.err rename to python/test-data-2.0/failing/testLiteral1.err index 1805a4e8..8789ae0b 100644 --- a/python/test-data/testLiteral1.err +++ b/python/test-data-2.0/failing/testLiteral1.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data/testLiteral1.py", line 3, in + File "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/testTypes1.out b/python/test-data-2.0/failing/testLiteral1.out similarity index 100% rename from python/test-data/testTypes1.out rename to python/test-data-2.0/failing/testLiteral1.out diff --git a/python/test-data/testLiteral1.py b/python/test-data-2.0/failing/testLiteral1.py similarity index 100% rename from python/test-data/testLiteral1.py rename to python/test-data-2.0/failing/testLiteral1.py diff --git a/python/test-data-2.0/failing/testLockFactory.err b/python/test-data-2.0/failing/testLockFactory.err new file mode 100644 index 00000000..82aad7e1 --- /dev/null +++ b/python/test-data-2.0/failing/testLockFactory.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testLockFactory.py +## Fehlerhafter Aufruf in Zeile 15: + +foo("not a lock") + +## Typ deklariert in Zeile 6: + +def foo(lock: wypp.LockFactory) -> None: diff --git a/python/test-data/testTypesCollections1.out b/python/test-data-2.0/failing/testLockFactory.out similarity index 100% rename from python/test-data/testTypesCollections1.out rename to python/test-data-2.0/failing/testLockFactory.out diff --git a/python/test-data/testLockFactory.py b/python/test-data-2.0/failing/testLockFactory.py similarity index 100% rename from python/test-data/testLockFactory.py rename to python/test-data-2.0/failing/testLockFactory.py diff --git a/python/test-data-2.0/failing/testLockFactory2.err b/python/test-data-2.0/failing/testLockFactory2.err new file mode 100644 index 00000000..9e8c8c7b --- /dev/null +++ b/python/test-data-2.0/failing/testLockFactory2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testLockFactory2.py +## Fehlerhafter Aufruf in Zeile 14: + +foo("not a lock") + +## Typ deklariert in Zeile 6: + +def foo(l: wypp.Lock) -> None: diff --git a/python/test-data/testTypesDict1.out b/python/test-data-2.0/failing/testLockFactory2.out similarity index 100% rename from python/test-data/testTypesDict1.out rename to python/test-data-2.0/failing/testLockFactory2.out diff --git a/python/test-data/testLockFactory2.py b/python/test-data-2.0/failing/testLockFactory2.py similarity index 100% rename from python/test-data/testLockFactory2.py rename to python/test-data-2.0/failing/testLockFactory2.py diff --git a/python/test-data/testTypesDict3.out b/python/test-data-2.0/failing/testLockFactory_ok.err similarity index 100% rename from python/test-data/testTypesDict3.out rename to python/test-data-2.0/failing/testLockFactory_ok.err diff --git a/python/test-data-2.0/failing/testLockFactory_ok.out b/python/test-data-2.0/failing/testLockFactory_ok.out new file mode 100644 index 00000000..9766475a --- /dev/null +++ b/python/test-data-2.0/failing/testLockFactory_ok.out @@ -0,0 +1 @@ +ok diff --git a/python/test-data-2.0/failing/testLockFactory_ok.py b/python/test-data-2.0/failing/testLockFactory_ok.py new file mode 100644 index 00000000..3d1e1613 --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/testMissingReturn.err b/python/test-data-2.0/failing/testMissingReturn.err new file mode 100644 index 00000000..39032b7e --- /dev/null +++ b/python/test-data-2.0/failing/testMissingReturn.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testMissingReturn.py", line 6, in + print(billigStrom(500)) + File "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 testMissingReturn.py +## Rückgabetyp deklariert in Zeile 3: + +def billigStrom(kwh: float) -> float: + +## Aufruf in Zeile 6 führt dazu, dass die Funktion keinen Wert zurückgibt: + +print(billigStrom(500)) diff --git a/python/test-data/testTypesDict4.out b/python/test-data-2.0/failing/testMissingReturn.out similarity index 100% rename from python/test-data/testTypesDict4.out rename to python/test-data-2.0/failing/testMissingReturn.out diff --git a/python/test-data/testMissingReturn.py b/python/test-data-2.0/failing/testMissingReturn.py similarity index 100% rename from python/test-data/testMissingReturn.py rename to python/test-data-2.0/failing/testMissingReturn.py diff --git a/python/test-data/testTypesHigherOrderFuns3.out b/python/test-data-2.0/failing/testOriginalTypeNames_ok.err similarity index 100% rename from python/test-data/testTypesHigherOrderFuns3.out rename to python/test-data-2.0/failing/testOriginalTypeNames_ok.err diff --git a/python/test-data-2.0/failing/testOriginalTypeNames_ok.out b/python/test-data-2.0/failing/testOriginalTypeNames_ok.out new file mode 100644 index 00000000..614cfd32 --- /dev/null +++ b/python/test-data-2.0/failing/testOriginalTypeNames_ok.out @@ -0,0 +1 @@ + diff --git a/python/test-data/testOriginalTypeNames.py b/python/test-data-2.0/failing/testOriginalTypeNames_ok.py similarity index 100% rename from python/test-data/testOriginalTypeNames.py rename to python/test-data-2.0/failing/testOriginalTypeNames_ok.py diff --git a/python/test-data/testRecordSetTypeForwardRef.err b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err similarity index 66% rename from python/test-data/testRecordSetTypeForwardRef.err rename to python/test-data-2.0/failing/testRecordSetTypeForwardRef.err index f1494801..2194265e 100644 --- a/python/test-data/testRecordSetTypeForwardRef.err +++ b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data/testRecordSetTypeForwardRef.py", line 15, in + File "testRecordSetTypeForwardRef.py", line 15, in m() - File "test-data/testRecordSetTypeForwardRef.py", line 13, in m + File "testRecordSetTypeForwardRef.py", line 13, in m r.x = "hello" WyppTypeError: got value of wrong type given: 'hello' diff --git a/python/test-data/testRecordSetTypeForwardRef.err-3.10 b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.10 similarity index 100% rename from python/test-data/testRecordSetTypeForwardRef.err-3.10 rename to python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.10 diff --git a/python/test-data/testRecordSetTypeForwardRef.err-3.11 b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.11 similarity index 100% rename from python/test-data/testRecordSetTypeForwardRef.err-3.11 rename to python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.11 diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 new file mode 100644 index 00000000..cb2cf0bd --- /dev/null +++ b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testRecordSetTypeForwardRef.py", line 15, in + m() + File "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 testRecordSetTypeForwardRef.py +## Fehlerhafte Zuweisung in Zeile 13: + + r.x = "hello" + +## Typ deklariert in Zeile 6: + + x: A diff --git a/python/test-data/testTypesProtos1.out b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.out similarity index 100% rename from python/test-data/testTypesProtos1.out rename to python/test-data-2.0/failing/testRecordSetTypeForwardRef.out diff --git a/python/test-data/testRecordSetTypeForwardRef.py b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.py similarity index 100% rename from python/test-data/testRecordSetTypeForwardRef.py rename to python/test-data-2.0/failing/testRecordSetTypeForwardRef.py diff --git a/python/test-data/testRecordSetTypes.err b/python/test-data-2.0/failing/testRecordSetTypes.err similarity index 68% rename from python/test-data/testRecordSetTypes.err rename to python/test-data-2.0/failing/testRecordSetTypes.err index 04ffa90e..3186a26a 100644 --- a/python/test-data/testRecordSetTypes.err +++ b/python/test-data-2.0/failing/testRecordSetTypes.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data/testRecordSetTypes.py", line 12, in + File "testRecordSetTypes.py", line 12, in m() - File "test-data/testRecordSetTypes.py", line 10, in m + File "testRecordSetTypes.py", line 10, in m r.x = "hello" WyppTypeError: got value of wrong type given: 'hello' diff --git a/python/test-data/testRecordSetTypes.err-3.10 b/python/test-data-2.0/failing/testRecordSetTypes.err-3.10 similarity index 100% rename from python/test-data/testRecordSetTypes.err-3.10 rename to python/test-data-2.0/failing/testRecordSetTypes.err-3.10 diff --git a/python/test-data/testRecordSetTypes.err-3.11 b/python/test-data-2.0/failing/testRecordSetTypes.err-3.11 similarity index 100% rename from python/test-data/testRecordSetTypes.err-3.11 rename to python/test-data-2.0/failing/testRecordSetTypes.err-3.11 diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 b/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 new file mode 100644 index 00000000..ac1f70d9 --- /dev/null +++ b/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testRecordSetTypes.py", line 12, in + m() + File "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 testRecordSetTypes.py +## Fehlerhafte Zuweisung in Zeile 10: + + r.x = "hello" + +## Typ deklariert in Zeile 5: + + x : int diff --git a/python/test-data/testTypesProtos2.out b/python/test-data-2.0/failing/testRecordSetTypes.out similarity index 100% rename from python/test-data/testTypesProtos2.out rename to python/test-data-2.0/failing/testRecordSetTypes.out diff --git a/python/test-data/testRecordSetTypes.py b/python/test-data-2.0/failing/testRecordSetTypes.py similarity index 100% rename from python/test-data/testRecordSetTypes.py rename to python/test-data-2.0/failing/testRecordSetTypes.py diff --git a/python/test-data/testRecordTypes.err b/python/test-data-2.0/failing/testRecordTypes.err similarity index 85% rename from python/test-data/testRecordTypes.err rename to python/test-data-2.0/failing/testRecordTypes.err index 84554590..766e93f1 100644 --- a/python/test-data/testRecordTypes.err +++ b/python/test-data-2.0/failing/testRecordTypes.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testRecordTypes.py", line 8, in + File "testRecordTypes.py", line 8, in p = Point(1, '5') WyppTypeError: got value of wrong type given: '5' diff --git a/python/test-data-2.0/failing/testRecordTypes.err-3.12 b/python/test-data-2.0/failing/testRecordTypes.err-3.12 new file mode 100644 index 00000000..7901b40a --- /dev/null +++ b/python/test-data-2.0/failing/testRecordTypes.err-3.12 @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testRecordTypes.py +## Fehlerhafter Aufruf in Zeile 8: + +p = Point(1, '5') + +## Typ deklariert in Zeile 6: + + y: int diff --git a/python/test-data/testTypesProtos3.out b/python/test-data-2.0/failing/testRecordTypes.out similarity index 100% rename from python/test-data/testTypesProtos3.out rename to python/test-data-2.0/failing/testRecordTypes.out diff --git a/python/test-data/testRecordTypes.py b/python/test-data-2.0/failing/testRecordTypes.py similarity index 100% rename from python/test-data/testRecordTypes.py rename to python/test-data-2.0/failing/testRecordTypes.py diff --git a/python/test-data-2.0/failing/testTodo.err b/python/test-data-2.0/failing/testTodo.err new file mode 100644 index 00000000..b31ff9e1 --- /dev/null +++ b/python/test-data-2.0/failing/testTodo.err @@ -0,0 +1,7 @@ +Traceback (most recent call last): + File "testTodo.py", line 3, in + todo() + File "writeYourProgram.py", line 297, in todo + raise errors.TodoError(msg) + +TODO diff --git a/python/test-data/testTypesProtos6.out b/python/test-data-2.0/failing/testTodo.out similarity index 100% rename from python/test-data/testTypesProtos6.out rename to python/test-data-2.0/failing/testTodo.out diff --git a/python/test-data/testTodo.py b/python/test-data-2.0/failing/testTodo.py similarity index 100% rename from python/test-data/testTodo.py rename to python/test-data-2.0/failing/testTodo.py diff --git a/python/test-data-2.0/failing/testTraceback.err b/python/test-data-2.0/failing/testTraceback.err new file mode 100644 index 00000000..0e6d16ea --- /dev/null +++ b/python/test-data-2.0/failing/testTraceback.err @@ -0,0 +1,6 @@ +Traceback (most recent call last): + File "testTraceback.py", line 9, in + foo(lst) + File "testTraceback.py", line 7, in foo + print(lst[10]) +IndexError: list index out of range diff --git a/python/test-data/testTraceback.out b/python/test-data-2.0/failing/testTraceback.out similarity index 100% rename from python/test-data/testTraceback.out rename to python/test-data-2.0/failing/testTraceback.out diff --git a/python/test-data/testTraceback.py b/python/test-data-2.0/failing/testTraceback.py similarity index 100% rename from python/test-data/testTraceback.py rename to python/test-data-2.0/failing/testTraceback.py diff --git a/python/test-data/testTraceback2.err b/python/test-data-2.0/failing/testTraceback2.err similarity index 58% rename from python/test-data/testTraceback2.err rename to python/test-data-2.0/failing/testTraceback2.err index 82834546..d87cc74a 100644 --- a/python/test-data/testTraceback2.err +++ b/python/test-data-2.0/failing/testTraceback2.err @@ -1,4 +1,4 @@ - File "test-data/testTraceback2.py", line 3 + File "testTraceback2.py", line 3 lst = [1,2,3 ^ SyntaxError: '[' was never closed diff --git a/python/test-data/testTraceback2.err-3.10.0 b/python/test-data-2.0/failing/testTraceback2.err-3.10.0 similarity index 100% rename from python/test-data/testTraceback2.err-3.10.0 rename to python/test-data-2.0/failing/testTraceback2.err-3.10.0 diff --git a/python/test-data/testTraceback2.err-3.9 b/python/test-data-2.0/failing/testTraceback2.err-3.9 similarity index 100% rename from python/test-data/testTraceback2.err-3.9 rename to python/test-data-2.0/failing/testTraceback2.err-3.9 diff --git a/python/test-data/testTypesProtos7.out b/python/test-data-2.0/failing/testTraceback2.out similarity index 100% rename from python/test-data/testTypesProtos7.out rename to python/test-data-2.0/failing/testTraceback2.out diff --git a/python/test-data/testTraceback2.py b/python/test-data-2.0/failing/testTraceback2.py similarity index 100% rename from python/test-data/testTraceback2.py rename to python/test-data-2.0/failing/testTraceback2.py diff --git a/python/test-data/testTraceback3.err b/python/test-data-2.0/failing/testTraceback3.err similarity index 61% rename from python/test-data/testTraceback3.err rename to python/test-data-2.0/failing/testTraceback3.err index 6a207b3e..f262f399 100644 --- a/python/test-data/testTraceback3.err +++ b/python/test-data-2.0/failing/testTraceback3.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data/testTraceback3.py", line 2, in + File "testTraceback3.py", line 2, in print([1,2,3][10]) IndexError: list index out of range diff --git a/python/test-data/testTypesProtos8.out b/python/test-data-2.0/failing/testTraceback3.out similarity index 100% rename from python/test-data/testTypesProtos8.out rename to python/test-data-2.0/failing/testTraceback3.out diff --git a/python/test-data/testTraceback3.py b/python/test-data-2.0/failing/testTraceback3.py similarity index 100% rename from python/test-data/testTraceback3.py rename to python/test-data-2.0/failing/testTraceback3.py diff --git a/python/test-data-2.0/failing/testTypes1.err b/python/test-data-2.0/failing/testTypes1.err new file mode 100644 index 00000000..e11aeef6 --- /dev/null +++ b/python/test-data-2.0/failing/testTypes1.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypes1.py +## Fehlerhafter Aufruf in Zeile 4: + +inc("1") + +## Typ deklariert in Zeile 1: + +def inc(x: int) -> int: diff --git a/python/test-data/testTypes1.err-notypes b/python/test-data-2.0/failing/testTypes1.err-notypes similarity index 100% rename from python/test-data/testTypes1.err-notypes rename to python/test-data-2.0/failing/testTypes1.err-notypes diff --git a/python/test-data/testTypesProtos9.out b/python/test-data-2.0/failing/testTypes1.out similarity index 100% rename from python/test-data/testTypesProtos9.out rename to python/test-data-2.0/failing/testTypes1.out diff --git a/python/test-data/testTypes1.py b/python/test-data-2.0/failing/testTypes1.py similarity index 100% rename from python/test-data/testTypes1.py rename to python/test-data-2.0/failing/testTypes1.py diff --git a/python/test-data/testTypesDict1.err b/python/test-data-2.0/failing/testTypesDict1.err similarity index 74% rename from python/test-data/testTypesDict1.err rename to python/test-data-2.0/failing/testTypesDict1.err index 4abbc36a..34181724 100644 --- a/python/test-data/testTypesDict1.err +++ b/python/test-data-2.0/failing/testTypesDict1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data/testTypesDict1.py", line 7, in + File "testTypesDict1.py", line 7, in appendSomething(d) - File "test-data/testTypesDict1.py", line 4, in appendSomething + File "testTypesDict1.py", line 4, in appendSomething d['x'] = "foo" WyppTypeError: got value of wrong type given: 'foo' diff --git a/python/test-data/testTypesSet1.out b/python/test-data-2.0/failing/testTypesDict1.out similarity index 100% rename from python/test-data/testTypesSet1.out rename to python/test-data-2.0/failing/testTypesDict1.out diff --git a/python/test-data-2.0/failing/testTypesDict3.err b/python/test-data-2.0/failing/testTypesDict3.err new file mode 100644 index 00000000..155259ab --- /dev/null +++ b/python/test-data-2.0/failing/testTypesDict3.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "testTypesDict3.py", line 11, in + foo({'y': func}) + File "testTypesDict3.py", line 8, in foo + return res + +WyppTypeError: ['xxx', 42] + +Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. + +## Datei testTypesDict3.py +## Rückgabetyp deklariert in Zeile 3: + +def foo(d: dict[str, Callable[[], str]]) -> list[str]: + +## Fehlerhaftes return in Zeile 8: + + return res + +## Aufruf in Zeile 11 verursacht das fehlerhafte return: + +foo({'y': func}) diff --git a/python/test-data/testTypesSet3.out b/python/test-data-2.0/failing/testTypesDict3.out similarity index 100% rename from python/test-data/testTypesSet3.out rename to python/test-data-2.0/failing/testTypesDict3.out diff --git a/python/test-data/testTypesDict3.py b/python/test-data-2.0/failing/testTypesDict3.py similarity index 100% rename from python/test-data/testTypesDict3.py rename to python/test-data-2.0/failing/testTypesDict3.py diff --git a/python/test-data-2.0/failing/testTypesDict4.err b/python/test-data-2.0/failing/testTypesDict4.err new file mode 100644 index 00000000..77e510b5 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesDict4.err @@ -0,0 +1,24 @@ +Traceback (most recent call last): + File "testTypesDict4.py", line 14, in + bar({'y': func}) # error + File "testTypesDict4.py", line 11, in bar + return foo(d) + File "testTypesDict4.py", line 8, in foo + return res + +WyppTypeError: [42, 'x'] + +Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. + +## Datei testTypesDict4.py +## Rückgabetyp deklariert in Zeile 3: + +def foo(d: dict[str, Callable[[], str]]) -> list[str]: + +## Fehlerhaftes return in Zeile 8: + + return res + +## Aufruf in Zeile 11 verursacht das fehlerhafte return: + + return foo(d) diff --git a/python/test-data/testTypesSubclassing1.out b/python/test-data-2.0/failing/testTypesDict4.out similarity index 100% rename from python/test-data/testTypesSubclassing1.out rename to python/test-data-2.0/failing/testTypesDict4.out diff --git a/python/test-data/testTypesDict4.py b/python/test-data-2.0/failing/testTypesDict4.py similarity index 100% rename from python/test-data/testTypesDict4.py rename to python/test-data-2.0/failing/testTypesDict4.py diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns.err b/python/test-data-2.0/failing/testTypesHigherOrderFuns.err new file mode 100644 index 00000000..a0feee64 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesHigherOrderFuns.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "testTypesHigherOrderFuns.py", line 10, in + map(["hello", "1"], lambda x: x) + File "testTypesHigherOrderFuns.py", line 7, in map + return res + +WyppTypeError: ['hello', '1'] + +Rückgabewert vom Typ `list[int]` erwartet bei Aufruf der Funktion `map`. + +## Datei testTypesHigherOrderFuns.py +## Rückgabetyp deklariert in Zeile 3: + +def map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]: + +## Fehlerhaftes return in Zeile 7: + + return res + +## Aufruf in Zeile 10 verursacht das fehlerhafte return: + +map(["hello", "1"], lambda x: x) diff --git a/python/test-data/testTypesHigherOrderFuns.out b/python/test-data-2.0/failing/testTypesHigherOrderFuns.out similarity index 100% rename from python/test-data/testTypesHigherOrderFuns.out rename to python/test-data-2.0/failing/testTypesHigherOrderFuns.out diff --git a/python/test-data/testTypesHigherOrderFuns.py b/python/test-data-2.0/failing/testTypesHigherOrderFuns.py similarity index 100% rename from python/test-data/testTypesHigherOrderFuns.py rename to python/test-data-2.0/failing/testTypesHigherOrderFuns.py diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err new file mode 100644 index 00000000..c5eee336 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypesHigherOrderFuns3.py +## Fehlerhafter Aufruf in Zeile 38: + +homePoints: Callable[[GameResult], int] = mkGamePoints(42) + +## Typ deklariert in Zeile 35: + +def mkGamePoints(cmp: Callable[[int, int], bool]) -> Callable[[GameResult], int]: diff --git a/python/test-data/testWrongKeywordArg.out b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.out similarity index 100% rename from python/test-data/testWrongKeywordArg.out rename to python/test-data-2.0/failing/testTypesHigherOrderFuns3.out diff --git a/python/test-data/testTypesHigherOrderFuns3.py b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.py similarity index 100% rename from python/test-data/testTypesHigherOrderFuns3.py rename to python/test-data-2.0/failing/testTypesHigherOrderFuns3.py diff --git a/python/test-data-2.0/failing/testTypesProtos1.err b/python/test-data-2.0/failing/testTypesProtos1.err new file mode 100644 index 00000000..20bd1e6b --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos1.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testTypesProtos1.py", line 21, in + doSomething(Dog()) + File "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 testTypesProtos1.py +## Fehlerhafter Aufruf in Zeile 19: + + print(a.makeSound(3.14)) + +## Typ deklariert in Zeile 13: + + def makeSound(self, loadness: int) -> str: diff --git a/python/test-data/testWrongKeywordArg2.out b/python/test-data-2.0/failing/testTypesProtos1.out similarity index 100% rename from python/test-data/testWrongKeywordArg2.out rename to python/test-data-2.0/failing/testTypesProtos1.out diff --git a/python/test-data/testTypesProtos1.py b/python/test-data-2.0/failing/testTypesProtos1.py similarity index 100% rename from python/test-data/testTypesProtos1.py rename to python/test-data-2.0/failing/testTypesProtos1.py diff --git a/python/test-data-2.0/failing/testTypesProtos2.err b/python/test-data-2.0/failing/testTypesProtos2.err new file mode 100644 index 00000000..7ad021e5 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos2.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testTypesProtos2.py", line 21, in + doSomething(Dog()) + File "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 testTypesProtos2.py +## Fehlerhafter Aufruf in Zeile 19: + + print(a.makeSound(3.14)) + +## Typ deklariert in Zeile 13: + + def makeSound(self, loadness: int) -> str: diff --git a/python/test-data/testWrongNumOfArguments.out b/python/test-data-2.0/failing/testTypesProtos2.out similarity index 100% rename from python/test-data/testWrongNumOfArguments.out rename to python/test-data-2.0/failing/testTypesProtos2.out diff --git a/python/test-data/testTypesProtos2.py b/python/test-data-2.0/failing/testTypesProtos2.py similarity index 100% rename from python/test-data/testTypesProtos2.py rename to python/test-data-2.0/failing/testTypesProtos2.py diff --git a/python/test-data/testTypesProtos3.err b/python/test-data-2.0/failing/testTypesProtos3.err similarity index 87% rename from python/test-data/testTypesProtos3.err rename to python/test-data-2.0/failing/testTypesProtos3.err index 0a3b18e1..397a679d 100644 --- a/python/test-data/testTypesProtos3.err +++ b/python/test-data-2.0/failing/testTypesProtos3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testTypesProtos3.py", line 21, in + File "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. diff --git a/python/test-data/testWrongNumOfArguments2.out b/python/test-data-2.0/failing/testTypesProtos3.out similarity index 100% rename from python/test-data/testWrongNumOfArguments2.out rename to python/test-data-2.0/failing/testTypesProtos3.out diff --git a/python/test-data/testTypesProtos3.py b/python/test-data-2.0/failing/testTypesProtos3.py similarity index 100% rename from python/test-data/testTypesProtos3.py rename to python/test-data-2.0/failing/testTypesProtos3.py diff --git a/python/test-data-2.0/failing/testTypesProtos4.err b/python/test-data-2.0/failing/testTypesProtos4.err new file mode 100644 index 00000000..cb06b422 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos4.err @@ -0,0 +1,21 @@ +Traceback (most recent call last): + File "testTypesProtos4.py", line 27, in + print(foo(ConcreteWrong())) + File "testTypesProtos4.py", line 24, in foo + return fn(2) + File "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 testTypesProtos4.py +## Fehlerhafter Aufruf in Zeile 20: + + return lambda x: bar(x) # invalid call of bar with argument of type int + +## Typ deklariert in Zeile 15: + +def bar(s: str) -> int: diff --git a/python/test-data/testTypesProtos4.out b/python/test-data-2.0/failing/testTypesProtos4.out similarity index 100% rename from python/test-data/testTypesProtos4.out rename to python/test-data-2.0/failing/testTypesProtos4.out diff --git a/python/test-data/testTypesProtos4.py b/python/test-data-2.0/failing/testTypesProtos4.py similarity index 100% rename from python/test-data/testTypesProtos4.py rename to python/test-data-2.0/failing/testTypesProtos4.py diff --git a/python/test-data-2.0/failing/testTypesProtos6.err b/python/test-data-2.0/failing/testTypesProtos6.err new file mode 100644 index 00000000..2dcd966c --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos6.err @@ -0,0 +1,25 @@ +Traceback (most recent call last): + File "testTypesProtos6.py", line 57, in + print(computeTotalSize(root)) + File "testTypesProtos6.py", line 50, in computeTotalSize + fs.accept(visitor) + File "testTypesProtos6.py", line 19, in accept + visitor.visitDirectory(self) + File "testTypesProtos6.py", line 41, in visitDirectory + c.accept(self) + File "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 testTypesProtos6.py +## Fehlerhafter Aufruf in Zeile 28: + + visitor.visitFile(self) + +## Typ deklariert in Zeile 42: + + def visitFile(self, file: str): diff --git a/python/test-data/wrong-caused-by.out b/python/test-data-2.0/failing/testTypesProtos6.out similarity index 100% rename from python/test-data/wrong-caused-by.out rename to python/test-data-2.0/failing/testTypesProtos6.out diff --git a/python/test-data/testTypesProtos6.py b/python/test-data-2.0/failing/testTypesProtos6.py similarity index 100% rename from python/test-data/testTypesProtos6.py rename to python/test-data-2.0/failing/testTypesProtos6.py diff --git a/python/test-data-2.0/failing/testTypesProtos7.err b/python/test-data-2.0/failing/testTypesProtos7.err new file mode 100644 index 00000000..9056e7df --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos7.err @@ -0,0 +1,25 @@ +Traceback (most recent call last): + File "testTypesProtos7.py", line 76, in + print(computeTotalSize(root)) + File "testTypesProtos7.py", line 69, in computeTotalSize + fs.accept(visitor) + File "testTypesProtos7.py", line 36, in accept + visitor.visitDirectory(self) + File "testTypesProtos7.py", line 60, in visitDirectory + c.accept(self) + File "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 testTypesProtos7.py +## Fehlerhafter Aufruf in Zeile 47: + + visitor.visitFile(self) + +## Typ deklariert in Zeile 61: + + def visitFile(self, f: str): diff --git a/python/test-data-2.0/failing/testTypesProtos7.out b/python/test-data-2.0/failing/testTypesProtos7.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesProtos7.py b/python/test-data-2.0/failing/testTypesProtos7.py similarity index 100% rename from python/test-data/testTypesProtos7.py rename to python/test-data-2.0/failing/testTypesProtos7.py diff --git a/python/test-data-2.0/failing/testTypesProtos8.err b/python/test-data-2.0/failing/testTypesProtos8.err new file mode 100644 index 00000000..33e24c24 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos8.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testTypesProtos8.py", line 12, in + bar(Sub()) + File "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 testTypesProtos8.py +## Fehlerhafter Aufruf in Zeile 10: + + b.foo(1, "foo") + +## Typ deklariert in Zeile 6: + + def foo(self, y: int, x: float): diff --git a/python/test-data-2.0/failing/testTypesProtos8.out b/python/test-data-2.0/failing/testTypesProtos8.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesProtos8.py b/python/test-data-2.0/failing/testTypesProtos8.py similarity index 100% rename from python/test-data/testTypesProtos8.py rename to python/test-data-2.0/failing/testTypesProtos8.py diff --git a/python/test-data-2.0/failing/testTypesProtos9.err b/python/test-data-2.0/failing/testTypesProtos9.err new file mode 100644 index 00000000..6d73ee5e --- /dev/null +++ b/python/test-data-2.0/failing/testTypesProtos9.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testTypesProtos9.py", line 12, in + bar(Sub()) + File "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 testTypesProtos9.py +## Fehlerhafter Aufruf in Zeile 10: + + b.foo(1, "foo") + +## Typ deklariert in Zeile 6: + + def foo(self, subX: int, subY: float): diff --git a/python/test-data-2.0/failing/testTypesProtos9.out b/python/test-data-2.0/failing/testTypesProtos9.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesProtos9.py b/python/test-data-2.0/failing/testTypesProtos9.py similarity index 100% rename from python/test-data/testTypesProtos9.py rename to python/test-data-2.0/failing/testTypesProtos9.py diff --git a/python/test-data/testTypesRecordInheritance.err b/python/test-data-2.0/failing/testTypesRecordInheritance.err similarity index 85% rename from python/test-data/testTypesRecordInheritance.err rename to python/test-data-2.0/failing/testTypesRecordInheritance.err index 34a56d0b..23bddaed 100644 --- a/python/test-data/testTypesRecordInheritance.err +++ b/python/test-data-2.0/failing/testTypesRecordInheritance.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testTypesRecordInheritance.py", line 18, in + File "testTypesRecordInheritance.py", line 18, in Point3D(1,2, "foo") WyppTypeError: got value of wrong type given: 'foo' diff --git a/python/test-data/testTypesRecordInheritance.err-3.10 b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.10 similarity index 100% rename from python/test-data/testTypesRecordInheritance.err-3.10 rename to python/test-data-2.0/failing/testTypesRecordInheritance.err-3.10 diff --git a/python/test-data/testTypesRecordInheritance.err-3.11 b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.11 similarity index 100% rename from python/test-data/testTypesRecordInheritance.err-3.11 rename to python/test-data-2.0/failing/testTypesRecordInheritance.err-3.11 diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 new file mode 100644 index 00000000..4f3ce320 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypesRecordInheritance.py +## Fehlerhafter Aufruf in Zeile 18: + +Point3D(1,2, "foo") + +## Typ deklariert in Zeile 11: + + z: int diff --git a/python/test-data/testTypesRecordInheritance.out b/python/test-data-2.0/failing/testTypesRecordInheritance.out similarity index 100% rename from python/test-data/testTypesRecordInheritance.out rename to python/test-data-2.0/failing/testTypesRecordInheritance.out diff --git a/python/test-data/testTypesRecordInheritance.py b/python/test-data-2.0/failing/testTypesRecordInheritance.py similarity index 100% rename from python/test-data/testTypesRecordInheritance.py rename to python/test-data-2.0/failing/testTypesRecordInheritance.py diff --git a/python/test-data-2.0/failing/testTypesReturn.err b/python/test-data-2.0/failing/testTypesReturn.err new file mode 100644 index 00000000..7a9b048f --- /dev/null +++ b/python/test-data-2.0/failing/testTypesReturn.err @@ -0,0 +1,23 @@ +Traceback (most recent call last): + File "testTypesReturn.py", line 8, in + foo(False) + File "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 testTypesReturn.py +## Rückgabetyp deklariert in Zeile 1: + +def foo(flag: bool) -> int: + +## Fehlerhaftes return in Zeile 6: + + return 'you stupid' + +## Aufruf in Zeile 8 verursacht das fehlerhafte return: + +foo(False) diff --git a/python/test-data/testTypesReturn.out b/python/test-data-2.0/failing/testTypesReturn.out similarity index 100% rename from python/test-data/testTypesReturn.out rename to python/test-data-2.0/failing/testTypesReturn.out diff --git a/python/test-data/testTypesReturn.py b/python/test-data-2.0/failing/testTypesReturn.py similarity index 100% rename from python/test-data/testTypesReturn.py rename to python/test-data-2.0/failing/testTypesReturn.py diff --git a/python/test-data-2.0/failing/testTypesSequence1.err b/python/test-data-2.0/failing/testTypesSequence1.err new file mode 100644 index 00000000..e181f3fc --- /dev/null +++ b/python/test-data-2.0/failing/testTypesSequence1.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypesSequence1.py +## Fehlerhafter Aufruf in Zeile 10: + +foo(1) # should fail + +## Typ deklariert in Zeile 3: + +def foo(seq: Sequence) -> None: diff --git a/python/test-data/testTypesSequence1.out b/python/test-data-2.0/failing/testTypesSequence1.out similarity index 100% rename from python/test-data/testTypesSequence1.out rename to python/test-data-2.0/failing/testTypesSequence1.out diff --git a/python/test-data/testTypesSequence1.py b/python/test-data-2.0/failing/testTypesSequence1.py similarity index 100% rename from python/test-data/testTypesSequence1.py rename to python/test-data-2.0/failing/testTypesSequence1.py diff --git a/python/test-data-2.0/failing/testTypesSequence2.err b/python/test-data-2.0/failing/testTypesSequence2.err new file mode 100644 index 00000000..3f7da9b7 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesSequence2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypesSequence2.py +## Fehlerhafter Aufruf in Zeile 11: + +foo("Hello!") # should fail + +## Typ deklariert in Zeile 3: + +def foo(seq: Sequence[int]) -> None: diff --git a/python/test-data/testTypesSequence2.out b/python/test-data-2.0/failing/testTypesSequence2.out similarity index 73% rename from python/test-data/testTypesSequence2.out rename to python/test-data-2.0/failing/testTypesSequence2.out index d19f48dc..5e77e5dc 100644 --- a/python/test-data/testTypesSequence2.out +++ b/python/test-data-2.0/failing/testTypesSequence2.out @@ -7,5 +7,3 @@ (4, 5) 4 5 -'Hello!' -Hello! diff --git a/python/test-data/testTypesSequence2.py b/python/test-data-2.0/failing/testTypesSequence2.py similarity index 100% rename from python/test-data/testTypesSequence2.py rename to python/test-data-2.0/failing/testTypesSequence2.py diff --git a/python/test-data/testTypesSet1.err b/python/test-data-2.0/failing/testTypesSet1.err similarity index 73% rename from python/test-data/testTypesSet1.err rename to python/test-data-2.0/failing/testTypesSet1.err index 8e8d6626..549bc2ae 100644 --- a/python/test-data/testTypesSet1.err +++ b/python/test-data-2.0/failing/testTypesSet1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data/testTypesSet1.py", line 7, in + File "testTypesSet1.py", line 7, in appendSomething(l) - File "test-data/testTypesSet1.py", line 4, in appendSomething + File "testTypesSet1.py", line 4, in appendSomething l.add("foo") WyppTypeError: got value of wrong type given: 'foo' diff --git a/python/test-data-2.0/failing/testTypesSet1.out b/python/test-data-2.0/failing/testTypesSet1.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/testTypesSet2.err b/python/test-data-2.0/failing/testTypesSet2.err new file mode 100644 index 00000000..becc55c0 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesSet2.err @@ -0,0 +1,22 @@ +Traceback (most recent call last): + File "testTypesSet2.py", line 10, in + foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int + File "testTypesSet2.py", line 7, in foo + return res + +WyppTypeError: [42, '1'] + +Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. + +## Datei testTypesSet2.py +## Rückgabetyp deklariert in Zeile 3: + +def foo(l: set[Callable[[], str]]) -> list[str]: + +## Fehlerhaftes return in Zeile 7: + + return res + +## Aufruf in Zeile 10 verursacht das fehlerhafte return: + +foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int diff --git a/python/test-data/testTypesSet2.out b/python/test-data-2.0/failing/testTypesSet2.out similarity index 100% rename from python/test-data/testTypesSet2.out rename to python/test-data-2.0/failing/testTypesSet2.out diff --git a/python/test-data/testTypesSet2.py b/python/test-data-2.0/failing/testTypesSet2.py similarity index 100% rename from python/test-data/testTypesSet2.py rename to python/test-data-2.0/failing/testTypesSet2.py diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.err b/python/test-data-2.0/failing/testTypesSubclassing1.err new file mode 100644 index 00000000..abc22c8b --- /dev/null +++ b/python/test-data-2.0/failing/testTypesSubclassing1.err @@ -0,0 +1,19 @@ +Traceback (most recent call last): + File "testTypesSubclassing1.py", line 29, in + feedAnimal(dog) + File "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 testTypesSubclassing1.py +## Fehlerhafter Aufruf in Zeile 26: + + a.feed(AnimalFood('some cat food')) + +## Typ deklariert in Zeile 22: + + def feed(self, food: DogFood) -> None: diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.out b/python/test-data-2.0/failing/testTypesSubclassing1.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesSubclassing1.py b/python/test-data-2.0/failing/testTypesSubclassing1.py similarity index 100% rename from python/test-data/testTypesSubclassing1.py rename to python/test-data-2.0/failing/testTypesSubclassing1.py diff --git a/python/test-data-2.0/failing/testTypesTuple1.err b/python/test-data-2.0/failing/testTypesTuple1.err new file mode 100644 index 00000000..b659b217 --- /dev/null +++ b/python/test-data-2.0/failing/testTypesTuple1.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 testTypesTuple1.py +## Fehlerhafter Aufruf in Zeile 5: + +foo(1) + +## Typ deklariert in Zeile 1: + +def foo(l: tuple[int, ...]) -> int: diff --git a/python/test-data/testTypesTuple1.out b/python/test-data-2.0/failing/testTypesTuple1.out similarity index 100% rename from python/test-data/testTypesTuple1.out rename to python/test-data-2.0/failing/testTypesTuple1.out diff --git a/python/test-data/testTypesTuple1.py b/python/test-data-2.0/failing/testTypesTuple1.py similarity index 100% rename from python/test-data/testTypesTuple1.py rename to python/test-data-2.0/failing/testTypesTuple1.py diff --git a/python/test-data/testWrongKeywordArg.err b/python/test-data-2.0/failing/testWrongKeywordArg.err similarity index 78% rename from python/test-data/testWrongKeywordArg.err rename to python/test-data-2.0/failing/testWrongKeywordArg.err index e33de87b..500e080c 100644 --- a/python/test-data/testWrongKeywordArg.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testWrongKeywordArg.py", line 4, in + File "testWrongKeywordArg.py", line 4, in foo(x=4) WyppTypeError: missing a required argument: 'kw' diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.out b/python/test-data-2.0/failing/testWrongKeywordArg.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testWrongKeywordArg.py b/python/test-data-2.0/failing/testWrongKeywordArg.py similarity index 100% rename from python/test-data/testWrongKeywordArg.py rename to python/test-data-2.0/failing/testWrongKeywordArg.py diff --git a/python/test-data/testWrongKeywordArg2.err b/python/test-data-2.0/failing/testWrongKeywordArg2.err similarity index 81% rename from python/test-data/testWrongKeywordArg2.err rename to python/test-data-2.0/failing/testWrongKeywordArg2.err index 95915241..07b5be20 100644 --- a/python/test-data/testWrongKeywordArg2.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data/testWrongKeywordArg2.py", line 8, in + File "testWrongKeywordArg2.py", line 8, in p = Point(foo=3, y=4) WyppTypeError: missing a required argument: 'x' diff --git a/python/test-data/testWrongKeywordArg2.err-3.10 b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.10 similarity index 100% rename from python/test-data/testWrongKeywordArg2.err-3.10 rename to python/test-data-2.0/failing/testWrongKeywordArg2.err-3.10 diff --git a/python/test-data/testWrongKeywordArg2.err-3.11 b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.11 similarity index 100% rename from python/test-data/testWrongKeywordArg2.err-3.11 rename to python/test-data-2.0/failing/testWrongKeywordArg2.err-3.11 diff --git a/python/test-data/testWrongKeywordArg2.err-3.12 b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 similarity index 100% rename from python/test-data/testWrongKeywordArg2.err-3.12 rename to python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.out b/python/test-data-2.0/failing/testWrongKeywordArg2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testWrongKeywordArg2.py b/python/test-data-2.0/failing/testWrongKeywordArg2.py similarity index 100% rename from python/test-data/testWrongKeywordArg2.py rename to python/test-data-2.0/failing/testWrongKeywordArg2.py diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.err b/python/test-data-2.0/failing/testWrongNumOfArguments.err new file mode 100644 index 00000000..74bb5cf3 --- /dev/null +++ b/python/test-data-2.0/failing/testWrongNumOfArguments.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "testWrongNumOfArguments.py", line 4, in + foo(1) + +WyppTypeError: Anzahl der Argument passt nicht + +Funktion `foo` benötigt 2 Argumente. +Gegeben: 1 Argument + +## Datei testWrongNumOfArguments.py +## Aufruf in Zeile 4: + +foo(1) diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.out b/python/test-data-2.0/failing/testWrongNumOfArguments.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testWrongNumOfArguments.py b/python/test-data-2.0/failing/testWrongNumOfArguments.py similarity index 100% rename from python/test-data/testWrongNumOfArguments.py rename to python/test-data-2.0/failing/testWrongNumOfArguments.py diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.err b/python/test-data-2.0/failing/testWrongNumOfArguments2.err new file mode 100644 index 00000000..91506c35 --- /dev/null +++ b/python/test-data-2.0/failing/testWrongNumOfArguments2.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "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 testWrongNumOfArguments2.py +## Aufruf in Zeile 6: + +c.foo(1) diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.out b/python/test-data-2.0/failing/testWrongNumOfArguments2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testWrongNumOfArguments2.py b/python/test-data-2.0/failing/testWrongNumOfArguments2.py similarity index 100% rename from python/test-data/testWrongNumOfArguments2.py rename to python/test-data-2.0/failing/testWrongNumOfArguments2.py diff --git a/python/test-data-2.0/failing/wrong-caused-by.err b/python/test-data-2.0/failing/wrong-caused-by.err new file mode 100644 index 00000000..50f93b92 --- /dev/null +++ b/python/test-data-2.0/failing/wrong-caused-by.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 wrong-caused-by.py +## Fehlerhafter Aufruf in Zeile 31: + +mainStreetM.turnIntoStreet(redCarM) + +## Typ deklariert in Zeile 10: + + def turnIntoStreet(self: StreetM, car: Car) -> None: diff --git a/python/test-data-2.0/failing/wrong-caused-by.out b/python/test-data-2.0/failing/wrong-caused-by.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/wrong-caused-by.py b/python/test-data-2.0/failing/wrong-caused-by.py similarity index 100% rename from python/test-data/wrong-caused-by.py rename to python/test-data-2.0/failing/wrong-caused-by.py 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/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/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/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-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-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/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/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/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/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/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-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-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-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/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/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 05fb7758..00000000 --- a/python/test-data/testTypesCollections1.py +++ /dev/null @@ -1,8 +0,0 @@ -from wypp import * - -def appendSomething(l: list[int]): - 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/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/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-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 - foo(False) -WyppTypeError: got value of wrong type -given: 'you stupid' -expected: value of type int - -context: foo(flag: bool) -> int - ^^^ -declared at: test-data/testTypesReturn.py:1 -caused by: test-data/testTypesReturn.py:6 - | return 'you stupid' diff --git a/python/test-data/testTypesSequence1.err b/python/test-data/testTypesSequence1.err deleted file mode 100644 index a24c922c..00000000 --- a/python/test-data/testTypesSequence1.err +++ /dev/null @@ -1,12 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSequence1.py", line 10, in - foo(1) # should fail -WyppTypeError: got value of wrong type -given: 1 -expected: value of type Sequence - -context: foo(seq: Sequence) -> None - ^^^^^^^^ -declared at: test-data/testTypesSequence1.py:3 -caused by: test-data/testTypesSequence1.py:10 - | foo(1) # should fail diff --git a/python/test-data/testTypesSequence2.err b/python/test-data/testTypesSequence2.err deleted file mode 100644 index f44365fc..00000000 --- a/python/test-data/testTypesSequence2.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSequence2.py", line 11, in - foo("Hello!") # should fail - File "test-data/testTypesSequence2.py", line 6, in foo - for x in seq: -WyppTypeError: str is not a Sequence[int] -given: 'Hello!' -expected: value of type Sequence[int] - -context: foo(seq: Sequence[int]) -> None - ^^^^^^^^^^^^^ -declared at: test-data/testTypesSequence2.py:3 -caused by: test-data/testTypesSequence2.py:11 - | foo("Hello!") # should fail diff --git a/python/test-data/testTypesSet2.err b/python/test-data/testTypesSet2.err deleted file mode 100644 index d082c7c7..00000000 --- a/python/test-data/testTypesSet2.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSet2.py", line 10, in - foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int - File "test-data/testTypesSet2.py", line 6, in foo - res.append(f()) -WyppTypeError: set is not a set[Callable[[], str]] -given: { at 0x00>, at 0x00>} -expected: value of type set[Callable[[], str]] - -context: foo(l: set[Callable[[], str]]) -> list[str] - ^^^^^^^^^^^^^^^^^^^^^^ -declared at: test-data/testTypesSet2.py:3 -caused by: test-data/testTypesSet2.py:10 - | foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int diff --git a/python/test-data/testTypesSet3.err b/python/test-data/testTypesSet3.err deleted file mode 100644 index 0185212d..00000000 --- a/python/test-data/testTypesSet3.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSet3.py", line 11, in - foo(set([func])) - File "test-data/testTypesSet3.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/testTypesSet3.py:4 -caused by: test-data/testTypesSet3.py:4 - | l.add(lambda: 42) # error diff --git a/python/test-data/testTypesSet3.py b/python/test-data/testTypesSet3.py deleted file mode 100644 index 5d183bdd..00000000 --- a/python/test-data/testTypesSet3.py +++ /dev/null @@ -1,11 +0,0 @@ -from wypp import * - -def foo(l: set[Callable[[], str]]) -> list[str]: - l.add(lambda: 42) # error - res = [] - for f in l: - res.append(f()) - return res - -func = lambda: "xxx" -foo(set([func])) diff --git a/python/test-data/testTypesSet4.err b/python/test-data/testTypesSet4.err deleted file mode 100644 index d5955512..00000000 --- a/python/test-data/testTypesSet4.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSet4.py", line 11, in - foo(set([func])) # error - File "test-data/testTypesSet4.py", line 6, in foo - res.append(f()) -WyppTypeError: set is not a set[Callable[[], str]] -given: { at 0x00>} -expected: value of type set[Callable[[], str]] - -context: foo(l: set[Callable[[], str]]) -> list[str] - ^^^^^^^^^^^^^^^^^^^^^^ -declared at: test-data/testTypesSet4.py:3 -caused by: test-data/testTypesSet4.py:11 - | foo(set([func])) # error diff --git a/python/test-data/testTypesSet4.out b/python/test-data/testTypesSet4.out deleted file mode 100644 index e56d69a9..00000000 --- a/python/test-data/testTypesSet4.out +++ /dev/null @@ -1 +0,0 @@ -['x'] diff --git a/python/test-data/testTypesSet4.py b/python/test-data/testTypesSet4.py deleted file mode 100644 index f8d7f7be..00000000 --- a/python/test-data/testTypesSet4.py +++ /dev/null @@ -1,11 +0,0 @@ -from wypp import * - -def foo(l: set[Callable[[], str]]) -> list[str]: - res = [] - for f in l: - res.append(f()) - return res - -print( foo(set([lambda: 'x']))) # ok -func = lambda: 42 -foo(set([func])) # error diff --git a/python/test-data/testTypesSubclassing1.err b/python/test-data/testTypesSubclassing1.err deleted file mode 100644 index 3c9aa638..00000000 --- a/python/test-data/testTypesSubclassing1.err +++ /dev/null @@ -1,28 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesSubclassing1.py", line 29, in - feedAnimal(dog) - File "test-data/testTypesSubclassing1.py", line 26, in feedAnimal - a.feed(AnimalFood('some cat food')) -WyppTypeError: Dog does not implement parent Animal -given: -expected: value of type Animal - -declared at: test-data/testTypesSubclassing1.py:20 -declared at: test-data/testTypesSubclassing1.py:10 -caused by: test-data/testTypesSubclassing1.py:20 - | class Dog(Animal): -caused by: test-data/testTypesSubclassing1.py:22 - | def feed(self, food: DogFood) -> None: - -Argument food of method feed violates the type declared by the parent Animal. -Annotation DogFood is incompatible with the parent's annotation AnimalFood. - -given: -expected: value of type DogFood - -context: feed(self: Self, food: DogFood) -> None - ^^^^^^^ -declared at: test-data/testTypesSubclassing1.py:22 -declared at: test-data/testTypesSubclassing1.py:10 -caused by: test-data/testTypesSubclassing1.py:22 - | def feed(self, food: DogFood) -> None: diff --git a/python/test-data/testTypesTuple1.err b/python/test-data/testTypesTuple1.err deleted file mode 100644 index 1e6e9a50..00000000 --- a/python/test-data/testTypesTuple1.err +++ /dev/null @@ -1,12 +0,0 @@ -Traceback (most recent call last): - File "test-data/testTypesTuple1.py", line 5, in - foo(1) -WyppTypeError: got value of wrong type -given: 1 -expected: value of type tuple[int, ...] - -context: foo(l: tuple[int, ...]) -> int - ^^^^^^^^^^^^^^^ -declared at: test-data/testTypesTuple1.py:1 -caused by: test-data/testTypesTuple1.py:5 - | foo(1) diff --git a/python/test-data/testWrongNumOfArguments.err b/python/test-data/testWrongNumOfArguments.err deleted file mode 100644 index 162142c8..00000000 --- a/python/test-data/testWrongNumOfArguments.err +++ /dev/null @@ -1,9 +0,0 @@ -Traceback (most recent call last): - File "test-data/testWrongNumOfArguments.py", line 4, in - foo(1) -WyppTypeError: missing a required argument: 'y' - -context: foo(x: int, y: float) -> float -declared at: test-data/testWrongNumOfArguments.py:1 -caused by: test-data/testWrongNumOfArguments.py:4 - | foo(1) diff --git a/python/test-data/testWrongNumOfArguments2.err b/python/test-data/testWrongNumOfArguments2.err deleted file mode 100644 index 21a479e3..00000000 --- a/python/test-data/testWrongNumOfArguments2.err +++ /dev/null @@ -1,9 +0,0 @@ -Traceback (most recent call last): - File "test-data/testWrongNumOfArguments2.py", line 6, in - c.foo(1) -WyppTypeError: missing a required argument: 'y' - -context: foo(self: Self, x: int, y: float) -> float -declared at: test-data/testWrongNumOfArguments2.py:2 -caused by: test-data/testWrongNumOfArguments2.py:6 - | c.foo(1) diff --git a/python/test-data/wrong-caused-by.err b/python/test-data/wrong-caused-by.err deleted file mode 100644 index 6b363385..00000000 --- a/python/test-data/wrong-caused-by.err +++ /dev/null @@ -1,12 +0,0 @@ -Traceback (most recent call last): - File "test-data/wrong-caused-by.py", line 31, in - mainStreetM.turnIntoStreet(redCarM) -WyppTypeError: got value of wrong type -given: CarM(licensePlate='OG PY 123', color='rot') -expected: value of type Car - -context: turnIntoStreet(self: Self, car: Car) -> None - ^^^ -declared at: test-data/wrong-caused-by.py:10 -caused by: test-data/wrong-caused-by.py:31 - | mainStreetM.turnIntoStreet(redCarM) diff --git a/python/tests/locationTestData.py b/python/tests/locationTestData.py index 4ca50ece..29a159d0 100644 --- a/python/tests/locationTestData.py +++ b/python/tests/locationTestData.py @@ -27,3 +27,14 @@ class TestRecord: x: int y: str z: float = 3.14 + +lineFooBase = 35 +lineFooSub = 39 + +class Base: + def foo(self, x: int, y: str): + pass + +class Sub(Base): + def foo(self, y: int, x: float): + pass diff --git a/python/tests/parsecacheTestData.py b/python/tests/parsecacheTestData.py new file mode 100644 index 00000000..7cf95236 --- /dev/null +++ b/python/tests/parsecacheTestData.py @@ -0,0 +1,25 @@ +# Keep line numbers intact, otherwise you have to adjust the tests. + +def bar(): + pass + +def foo(): + pass + +class C: + x: int + y: str + +class D: + x: int + z: str + def foo(self): + pass + +def spam() -> int: + return 1 + +def spam() -> int: + def foo(): + pass + return 2 diff --git a/python/tests/sample.py b/python/tests/sample.py index 975593c7..d117e9fb 100644 --- a/python/tests/sample.py +++ b/python/tests/sample.py @@ -1,11 +1,8 @@ -from writeYourProgram import * -import math -import typing +from wypp import * Drink = Literal["Tea", "Coffee"] # berechnet wieviele Tassen ich von einem Getränk trinken darf -@typechecked def canDrink(d: Drink) -> int: if d == "Tea": return 5 @@ -16,7 +13,7 @@ def canDrink(d: Drink) -> int: # - a circle (Circle) # - a square (Square) # - an overlay of two shapes (Overlay) -type Shape = typing.Union['Circle', 'Square', 'Overlay'] +type Shape = Union['Circle', 'Square', 'Overlay'] # A point consists of # - x (float) @@ -93,7 +90,6 @@ class Overlay: o2 = Overlay(o1, c2) # Calculate the distance between two points -@typechecked def distance(p1: Point, p2: Point) -> float: w = p1.x - p2.x h = p1.y - p2.y @@ -101,7 +97,6 @@ def distance(p1: Point, p2: Point) -> float: return dist # Is a point within a shape? -@typechecked def pointInShape(point: Point, shape: Shape) -> bool: px = point.x py = point.y @@ -119,4 +114,19 @@ def pointInShape(point: Point, shape: Shape) -> bool: elif type(shape) == Overlay: return pointInShape(point, shape.top) or pointInShape(point, shape.bottom) else: - return uncoveredCase() + 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/tests/test_location.py b/python/tests/test_location.py index a7fa5597..e4ed81ab 100644 --- a/python/tests/test_location.py +++ b/python/tests/test_location.py @@ -41,6 +41,20 @@ def test_StdCallableInfo2(self): argLoc = info.getParamSourceLocation("depth") self.assertLocation(argLoc, lineNoMyFunNoResultDef, 18, lineNoMyFunNoResultDef, 28) + def test_StdCallableInfoSub(self): + sub = Sub() + info = location.StdCallableInfo(sub.foo, location.ClassMember('method', 'Sub')) + self.assertEqual('locationTestData.py', os.path.basename(info.file)) + argLoc = info.getParamSourceLocation("y") + self.assertLocation(argLoc, lineFooSub, 18, lineFooSub, 24) + + def test_StdCallableInfoBase(self): + b = Base() + info = location.StdCallableInfo(b.foo, location.ClassMember('method', 'Base')) + self.assertEqual('locationTestData.py', os.path.basename(info.file)) + argLoc = info.getParamSourceLocation("y") + self.assertLocation(argLoc, lineFooBase, 26, lineFooBase, 32) + def test_RecordConstructorInfo(self): info = location.RecordConstructorInfo(TestRecord) # Test getting parameter source locations with precise line/column numbers diff --git a/python/tests/test_parsecache.py b/python/tests/test_parsecache.py index afab32db..d9c0d6ec 100644 --- a/python/tests/test_parsecache.py +++ b/python/tests/test_parsecache.py @@ -1,38 +1,48 @@ -# Test data - -def bar(): - pass - -def foo(): - pass - -class C: - x: int - y: str - -class D: - x: int - z: str - import unittest import parsecache +from parsecache import FunMatcher +import ast class TestParseCache(unittest.TestCase): def test_parseCacheFunDef(self): - a = parsecache.getAST('tests/test_parsecache.py') + a = parsecache.AST('tests/parsecacheTestData.py') assert(a is not None) - defFoo = a.getFunDef('foo') + defFoo = a.getFunDef(FunMatcher('foo', 6)) assert(defFoo is not None) self.assertEqual(defFoo.lineno, 6) - defBar = a.getFunDef('bar') + defBar = a.getFunDef(FunMatcher('bar')) assert(defBar is not None) self.assertEqual(defBar.lineno, 3) - x = a.getFunDef('spam') + x = a.getFunDef(FunMatcher('spam')) # conflicting def + self.assertIsNone(x) + defSpam = a.getFunDef(FunMatcher('spam', 22)) + assert(defSpam is not None) + self.assertEqual(defSpam.lineno, 22) + match defSpam.body[1]: + case ast.Return(ast.Constant(2)): + pass + case stmt: + self.fail(f'Invalid first statement of spam: {stmt}') + x = a.getFunDef(FunMatcher('egg')) self.assertIsNone(x) + def test_parseCacheNestedFunDef(self): + a = parsecache.AST('tests/parsecacheTestData.py') + assert(a is not None) + defFoo = a.getFunDef(FunMatcher('foo', 23)) + assert(defFoo is not None) + self.assertEqual(defFoo.lineno, 23) + + def test_parseCacheMethod(self): + a = parsecache.AST('tests/parsecacheTestData.py') + assert(a is not None) + defFoo = a.getMethodDef('D', FunMatcher('foo', 16)) + assert(defFoo is not None) + self.assertEqual(defFoo.lineno, 16) + def test_parseCacheRecordAttr(self): - a = parsecache.getAST('tests/test_parsecache.py') + a = parsecache.AST('tests/parsecacheTestData.py') assert(a is not None) # Test getting attributes from class C diff --git a/python/tests/test_record.py b/python/tests/test_record.py index 20a8b3db..d0113e9c 100644 --- a/python/tests/test_record.py +++ b/python/tests/test_record.py @@ -3,9 +3,9 @@ import sys import traceback import dataclasses +import stacktrace initModule() -setDieOnCheckFailures(True) @record class Point: @@ -28,6 +28,16 @@ class Box: class TestRecords(unittest.TestCase): + def setUp(self): + setDieOnCheckFailures(True) + self.original_profile = sys.getprofile() + stacktrace.installProfileHook() + + def tearDown(self): + # Restore original profile function + sys.setprofile(self.original_profile) + setDieOnCheckFailures(False) + def test_create(self): p1 = Point(1, 2) p2 = Point(3, 4) @@ -79,7 +89,6 @@ def test_eq2(self): # check should use special logic for floats check(p1, p2) - def test_hash(self): p1 = Point(1, 2) p2 = Point(1, 4) @@ -117,7 +126,7 @@ def test_addField(self): b = Box(5) try: b.foobar = 'foobar' - self.fail("Expected AttributeError") + self.fail(f"Expected AttributeError, b={b}") except AttributeError: pass p1 = Point(1, 2) diff --git a/python/tests/test_sample.py b/python/tests/test_sample.py deleted file mode 100644 index 65ab52b0..00000000 --- a/python/tests/test_sample.py +++ /dev/null @@ -1,22 +0,0 @@ -from writeYourProgram import * -import unittest -from sample import * - -setDieOnCheckFailures(True) - -class TestSample(unittest.TestCase): - def test_sample(self): - 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/tests/test_stacktrace.py b/python/tests/test_stacktrace.py index c35c3d0f..a39b7522 100644 --- a/python/tests/test_stacktrace.py +++ b/python/tests/test_stacktrace.py @@ -8,30 +8,28 @@ class TestReturnTracker(unittest.TestCase): def setUp(self): - # Save original profile function self.original_profile = sys.getprofile() def tearDown(self): - # Restore original profile function sys.setprofile(self.original_profile) def assertReturnFrame(self, tracker: stacktrace.ReturnTracker, line: int): - frame = tracker.getReturnFrame() + frame = tracker.getReturnFrame(0) assert(frame is not None) self.assertEqual(os.path.basename(frame.filename), 'stacktraceTestData.py') self.assertEqual(frame.lineno, line) def test_returnTracker1(self): - tracker = stacktrace.installProfileHook() + tracker = stacktrace.installProfileHook(1) f1() self.assertReturnFrame(tracker, f1ReturnLine) def test_returnTracker2(self): - tracker = stacktrace.installProfileHook() + tracker = stacktrace.installProfileHook(1) f2() self.assertReturnFrame(tracker, f2ReturnLine) def test_returnTracker3(self): - tracker = stacktrace.installProfileHook() + tracker = stacktrace.installProfileHook(1) f3() self.assertReturnFrame(tracker, f3ReturnLine) diff --git a/python/tests/test_utils.py b/python/tests/test_utils.py new file mode 100644 index 00000000..2f517b6c --- /dev/null +++ b/python/tests/test_utils.py @@ -0,0 +1,12 @@ +import unittest +from src.utils import dropWhile + +class TestUtils(unittest.TestCase): + + def test_dropWhile(self): + self.assertEqual(dropWhile([1, 2, 3, 4, 5], lambda x: x < 4), [4, 5]) + self.assertEqual(dropWhile([], lambda x: x < 10), []) + # Test where no elements satisfy the condition + self.assertEqual(dropWhile([5, 6, 7, 8], lambda x: x < 3), [5, 6, 7, 8]) + # Test where all elements satisfy the condition + self.assertEqual(dropWhile([1, 2, 3, 4], lambda x: x < 10), []) From d56790de84e1e626e1c517b5e65800c32c37c92a Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 14:20:56 +0200 Subject: [PATCH 41/56] typeguard: support for type aliases --- python/deps/typeguard/_checkers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/deps/typeguard/_checkers.py b/python/deps/typeguard/_checkers.py index 32de89de..d3875f7a 100644 --- a/python/deps/typeguard/_checkers.py +++ b/python/deps/typeguard/_checkers.py @@ -889,6 +889,15 @@ 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, @@ -905,6 +914,7 @@ def check_type_internal( :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: From 63e6866fa546f110cc22ad0c24d24a13feaa6661 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 15:03:33 +0200 Subject: [PATCH 42/56] all file tests working except one --- python/TODO_nowrappers.md | 16 ++------- python/fileTestsLib.py | 14 +++++--- python/src/errors.py | 27 ++++++++++++++- python/src/i18n.py | 26 ++++++++++++++- python/src/location.py | 5 +++ python/src/myLogging.py | 3 ++ python/src/runner.py | 1 + python/src/typecheck.py | 24 +++++++++----- python/src/writeYourProgram.py | 31 +++++++++++++++++- python/test-data-2.0/basics/typeAlias.err | 17 ++++++++++ python/test-data-2.0/basics/typeAlias.err_en | 17 ++++++++++ python/test-data-2.0/basics/typeAlias.out | 0 python/test-data-2.0/basics/typeAlias.out_en | 0 python/test-data-2.0/basics/typeAlias.py | 7 ++++ python/test-data-2.0/basics/typeAlias2.err | 16 +++++++++ python/test-data-2.0/basics/typeAlias2.err_en | 16 +++++++++ python/test-data-2.0/basics/typeAlias2.out | 0 python/test-data-2.0/basics/typeAlias2.out_en | 0 python/test-data-2.0/basics/typeAlias2.py | 7 ++++ .../failing/.invalidType3.py.swp | Bin 0 -> 12288 bytes python/test-data-2.0/failing/invalidType.err | 12 +++++++ python/test-data-2.0/failing/invalidType.out | 0 python/test-data-2.0/failing/invalidType.py | 9 +++++ python/test-data-2.0/failing/invalidType2.err | 4 +++ python/test-data-2.0/failing/invalidType2.out | 0 python/test-data-2.0/failing/invalidType2.py | 11 +++++++ python/test-data-2.0/failing/invalidType3.err | 12 +++++++ python/test-data-2.0/failing/invalidType3.out | 0 python/test-data-2.0/failing/invalidType3.py | 9 +++++ python/test-data-2.0/failing/invalidType4.err | 10 ++++++ python/test-data-2.0/failing/invalidType4.out | 0 python/test-data-2.0/failing/invalidType4.py | 9 +++++ python/test-data-2.0/failing/main.err | 29 +++++++++------- python/test-data-2.0/failing/main.py | 1 + .../test-data-2.0/failing/testGetSource.err | 14 +++++++- .../failing/testHintParentheses3.err | 16 +++++---- python/test-data-2.0/failing/testLiteral1.err | 14 +++++++- .../failing/testWrongKeywordArg.err | 13 +++++--- .../failing/testWrongKeywordArg2.err-3.12 | 15 +++++---- .../failing/testWrongKeywordArg3.err | 12 +++++++ .../failing/testWrongKeywordArg3.out | 0 .../failing/testWrongKeywordArg3.py | 8 +++++ .../failing/testWrongKeywordArg4.err | 12 +++++++ .../failing/testWrongKeywordArg4.out | 0 .../failing/testWrongKeywordArg4.py | 4 +++ .../failing/testWrongKeywordArg5.err | 13 ++++++++ .../failing/testWrongKeywordArg5.out | 0 .../failing/testWrongKeywordArg5.py | 4 +++ python/test-data-2.0/modules/B/mod.py | 5 +++ 49 files changed, 402 insertions(+), 61 deletions(-) create mode 100644 python/test-data-2.0/basics/typeAlias.err create mode 100644 python/test-data-2.0/basics/typeAlias.err_en create mode 100644 python/test-data-2.0/basics/typeAlias.out create mode 100644 python/test-data-2.0/basics/typeAlias.out_en create mode 100644 python/test-data-2.0/basics/typeAlias.py create mode 100644 python/test-data-2.0/basics/typeAlias2.err create mode 100644 python/test-data-2.0/basics/typeAlias2.err_en create mode 100644 python/test-data-2.0/basics/typeAlias2.out create mode 100644 python/test-data-2.0/basics/typeAlias2.out_en create mode 100644 python/test-data-2.0/basics/typeAlias2.py create mode 100644 python/test-data-2.0/failing/.invalidType3.py.swp create mode 100644 python/test-data-2.0/failing/invalidType.err create mode 100644 python/test-data-2.0/failing/invalidType.out create mode 100644 python/test-data-2.0/failing/invalidType.py create mode 100644 python/test-data-2.0/failing/invalidType2.err create mode 100644 python/test-data-2.0/failing/invalidType2.out create mode 100644 python/test-data-2.0/failing/invalidType2.py create mode 100644 python/test-data-2.0/failing/invalidType3.err create mode 100644 python/test-data-2.0/failing/invalidType3.out create mode 100644 python/test-data-2.0/failing/invalidType3.py create mode 100644 python/test-data-2.0/failing/invalidType4.err create mode 100644 python/test-data-2.0/failing/invalidType4.out create mode 100644 python/test-data-2.0/failing/invalidType4.py create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg3.err create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg3.out create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg3.py create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg4.err create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg4.out create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg4.py create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg5.err create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg5.out create mode 100644 python/test-data-2.0/failing/testWrongKeywordArg5.py create mode 100644 python/test-data-2.0/modules/B/mod.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index a853b75e..a1c47985 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,7 +1,9 @@ -* Fix file tests in test-data +* Fix file tests in test-data-2.0 * proper error messages for invalid @record * Avoid absolute paths in error messages + * Integration tests + * Installation * Typechecked console * Debug slow startup times @@ -12,18 +14,6 @@ Problematic tests: test-data-2.0/failing/testTypesProtos3.py # non-static methods without self - test-data-2.0/failing/testHintParentheses3.py # Union(list, str) - - test-data-2.0/failing/testWrongKeywordArg2.py # problem with keyword args - test-data-2.0/failing/testWrongKeywordArg.py # problem with keyword args - - test-data-2.0/failing/main.py # modules - - test-data-2.0/failing/testGetSource.py # Literal - test-data-2.0/failing/testLiteral1.py # Literal - - - test-data-2.0/failing/testArgs_ok.py # absolute filenames diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index ba633550..c1234fb8 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -318,9 +318,10 @@ def guessExitCode(testFile: str) -> int: class WyppTestConfig: typecheck: Literal[True, False, "both"] args: list[str] + pythonPath: Optional[str] @staticmethod def default() -> WyppTestConfig: - return WyppTestConfig(typecheck=True, args=[]) + return WyppTestConfig(typecheck=True, args=[], pythonPath=None) def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: """ @@ -328,7 +329,7 @@ def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: `max_lines` lines of the file at `path` and return it as a dict. Returns {} if not present. """ - validKeys = ['typecheck', 'args'] + validKeys = ['typecheck', 'args', 'pythonPath'] with open(path, "r", encoding="utf-8") as f: for lineno in range(1, max_lines + 1): line = f.readline() @@ -343,7 +344,8 @@ def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig: raise ValueError(f'Unknown key {k} in config for file {path}') typecheck = j.get('typecheck', True) args = j.get('args', []) - return WyppTestConfig(typecheck=typecheck, args=args) + pythonPath = j.get('pythonPath') + return WyppTestConfig(typecheck=typecheck, args=args, pythonPath=pythonPath) return WyppTestConfig.default() def checkNoConfig(testFile: str, @@ -365,12 +367,14 @@ def checkNoConfig(testFile: str, def check(testFile: str, exitCode: int = 1, - pythonPath: list[str] = [], minVersion: Optional[tuple[int, int]] = None, checkOutputs: bool = True, ctx: TestContext = globalCtx,): 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, @@ -398,6 +402,8 @@ def record(testFile: str): typecheck = True args = cfg.args pythonPath = [] + if cfg.pythonPath: + pythonPath = cfg.pythonPath.split(':') what = '' ctx = globalCtx def display(filename: str, where: str): diff --git a/python/src/errors.py b/python/src/errors.py index fdfaba23..e36e844a 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -45,6 +45,13 @@ def shouldReportTyMismatch(expected: Any, given: Any) -> bool: 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] = []): @@ -58,14 +65,32 @@ def __str__(self): @staticmethod def invalidType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError: lines = [] - lines.append(i18n.invalidTy(ty)) + 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: + lines.append('') + lines.append(f'## {i18n.tr("File")} {callLoc.filename}') + lines.append(f'## {i18n.tr("Problematic call in line")} {callLoc.startLine}:\n') + lines.append(renderLoc(callLoc)) + raise WyppTypeError('\n'.join(lines)) + @staticmethod def resultError(callableName: location.CallableName, resultTypeLoc: Optional[location.Loc], resultTy: Any, returnLoc: Optional[location.Loc], givenValue: Any, diff --git a/python/src/i18n.py b/python/src/i18n.py index 91216bac..82a644a9 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -129,7 +129,15 @@ def tr(key: str, **kws) -> str: '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}`.' } def expectingNoReturn(cn: location.CallableName) -> str: @@ -316,6 +324,22 @@ def noTypeAnnotationForAttribute(attrName: str, recordName: str) -> str: 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/src/location.py b/python/src/location.py index 4fa04b91..14a198db 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -151,6 +151,9 @@ def __init__(self, f: Callable, kind: CallableKind): 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 @@ -232,6 +235,8 @@ class RecordConstructorInfo(CallableInfo): 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__ diff --git a/python/src/myLogging.py b/python/src/myLogging.py index 10faa332..88f20469 100644 --- a/python/src/myLogging.py +++ b/python/src/myLogging.py @@ -4,6 +4,9 @@ VERBOSE = False # set via commandline DEBUG = utils.getEnv("WYPP_DEBUG", bool, False) +def isDebug() -> bool: + return DEBUG + def enableVerbose(): global VERBOSE VERBOSE = True diff --git a/python/src/runner.py b/python/src/runner.py index 0b38af9a..8ccc36ae 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -320,6 +320,7 @@ def runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): if not extraDirs: extraDirs = [] modDir = os.path.dirname(fileToRun) + print(f'fileToRun={fileToRun}, modDir={modDir}') with RunSetup(modDir, [fileToRun] + args): with instrument.setupFinder(modDir, extraDirs, doTypecheck): modName = os.path.basename(os.path.splitext(fileToRun)[0]) diff --git a/python/src/typecheck.py b/python/src/typecheck.py index a5543d64..bd598077 100644 --- a/python/src/typecheck.py +++ b/python/src/typecheck.py @@ -1,15 +1,14 @@ from __future__ import annotations -import sys from collections.abc import Callable from typing import ParamSpec, TypeVar, Any, Optional, Literal import inspect -import typing 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]) @@ -28,8 +27,11 @@ def isEmptySignature(sig: inspect.Signature) -> bool: def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool: match res: case MatchesTyFailure(exc, ty): - # raise exc - raise errors.WyppTypeError.invalidType(ty, tyLoc) + 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 @@ -93,14 +95,15 @@ def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any, 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(): - callLoc = None if not fi else location.Loc.fromFrameInfo(fi) - cn = location.CallableName.mk(info) raise errors.WyppTypeError.argCountMismatch(cn, callLoc, len(paramNames) - offset, @@ -116,15 +119,18 @@ def raiseArgMismatch(): 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, code: location.CallableInfo, cfg: CheckCfg) -> None: + result: Any, info: location.CallableInfo, cfg: CheckCfg) -> None: t = sig.return_annotation if isEmptyAnnotation(t): t = None - locDecl = code.getResultTypeLocation() + 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: @@ -134,7 +140,7 @@ def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo] if returnFrame: returnLoc = location.Loc.fromFrameInfo(returnFrame) extraFrames = [returnFrame] - raise errors.WyppTypeError.resultError(location.CallableName.mk(code), locDecl, t, returnLoc, result, + raise errors.WyppTypeError.resultError(location.CallableName.mk(info), locDecl, t, returnLoc, result, locRes, extraFrames) diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index 0240e72c..45b7052f 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -4,6 +4,9 @@ import errors import typecheck import records +import stacktrace +import renderTy +import location _DEBUG = False def _debug(s): @@ -54,6 +57,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), @@ -62,12 +67,36 @@ def _literalInstanceOf(self, value): return True return False -# 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: 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__ + else: + name = str(self) + typingPrefix = 'typing.' + if name.startswith(typingPrefix): + name = name[len(typingPrefix):] + def formatArg(x): + if name == 'Literal': + return repr(x) + else: + 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 errors.WyppTypeError.invalidType(tyStr, loc) + #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}]?") + +# 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 typechecked(func=None): def wrap(func): if hasattr(func, '__qualname__'): diff --git a/python/test-data-2.0/basics/typeAlias.err b/python/test-data-2.0/basics/typeAlias.err new file mode 100644 index 00000000..cc67722d --- /dev/null +++ b/python/test-data-2.0/basics/typeAlias.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 typeAlias.py +## Fehlerhafter Aufruf in Zeile 7: + +foo('foo') + +## Typ deklariert in Zeile 3: + +def foo(i: T) -> T: diff --git a/python/test-data-2.0/basics/typeAlias.err_en b/python/test-data-2.0/basics/typeAlias.err_en new file mode 100644 index 00000000..67a934d1 --- /dev/null +++ b/python/test-data-2.0/basics/typeAlias.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "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 typeAlias.py +## Problematic call in line 7: + +foo('foo') + +## Type declared in line 3: + +def foo(i: T) -> T: diff --git a/python/test-data-2.0/basics/typeAlias.out b/python/test-data-2.0/basics/typeAlias.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/basics/typeAlias.out_en b/python/test-data-2.0/basics/typeAlias.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/basics/typeAlias.py b/python/test-data-2.0/basics/typeAlias.py new file mode 100644 index 00000000..04a0bcd3 --- /dev/null +++ b/python/test-data-2.0/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/test-data-2.0/basics/typeAlias2.err b/python/test-data-2.0/basics/typeAlias2.err new file mode 100644 index 00000000..7221683a --- /dev/null +++ b/python/test-data-2.0/basics/typeAlias2.err @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "typeAlias2.py", line 7, in + foo(['foo']) + +WyppTypeError: ['foo'] + +Der Aufruf der Funktion `foo` erwartet Wert vom Typ `list[T]` als erstes Argument. + +## Datei typeAlias2.py +## Fehlerhafter Aufruf in Zeile 7: + +foo(['foo']) + +## Typ deklariert in Zeile 3: + +def foo(x: list[T]): diff --git a/python/test-data-2.0/basics/typeAlias2.err_en b/python/test-data-2.0/basics/typeAlias2.err_en new file mode 100644 index 00000000..2363f6c3 --- /dev/null +++ b/python/test-data-2.0/basics/typeAlias2.err_en @@ -0,0 +1,16 @@ +Traceback (most recent call last): + File "typeAlias2.py", line 7, in + foo(['foo']) + +WyppTypeError: ['foo'] + +The call of function `foo` expects value of type `list[T]` as 1st argument. + +## File typeAlias2.py +## Problematic call in line 7: + +foo(['foo']) + +## Type declared in line 3: + +def foo(x: list[T]): diff --git a/python/test-data-2.0/basics/typeAlias2.out b/python/test-data-2.0/basics/typeAlias2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/basics/typeAlias2.out_en b/python/test-data-2.0/basics/typeAlias2.out_en new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/basics/typeAlias2.py b/python/test-data-2.0/basics/typeAlias2.py new file mode 100644 index 00000000..69881c0a --- /dev/null +++ b/python/test-data-2.0/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-2.0/failing/.invalidType3.py.swp b/python/test-data-2.0/failing/.invalidType3.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..c9d32e78e7e84d6f642382f470a6a6a47da2e1fc GIT binary patch literal 12288 zcmeI&J#W-77zc2dwcvF1KF8W*g%kf*{{I)2b4PQQcPLH#Z;BrfduOGjazdrxo{`6gl zu+XJn_Wz^2adoYI;e4JxdN3Ykg*WkCHghK%>rT&aD06+RE4koFI_+HR)2551vuWeq z+^brynlkDT(_BTB?q)+-C{r0bm&P8e%H)%-(c`S?HlBbc1R!v!KpTs6@46W8j)u$e z-p*aRbNlepspy0N1Rwwb2tWV=5P$##{!f9}Y>6{|wQK9w+g{gO8}+)03IY&-00bZa z0SG_<0uX=z1Rwx`D=46HA%5NvV&^7rp8x+}eEO>;^_G&8MC7`l^ASp)&}3iqXu{{! z10}0D2?-JTj5eOg`~6_r!*HQ%{MFSMpI#wP4bIXAIvXBnTV zm*K6Ohndp;FNft#2(1qC;YbwT)wJlEhD_Z!AL-tzYdS4j-nO1jF9wvgE~?0z3gQnw C;))#r literal 0 HcmV?d00001 diff --git a/python/test-data-2.0/failing/invalidType.err b/python/test-data-2.0/failing/invalidType.err new file mode 100644 index 00000000..bd1107ed --- /dev/null +++ b/python/test-data-2.0/failing/invalidType.err @@ -0,0 +1,12 @@ +Traceback (most recent call last): + File "invalidType.py", line 9, in + foo() + +WyppTypeError: ungültiger Typ `Union(list(int), list[float])` + +Wolltest du `Union[list(int), list[float]]` schreiben? + +## Datei invalidType.py +## Typ deklariert in Zeile 6: + +def foo() -> Union(list(int), list[float]): diff --git a/python/test-data-2.0/failing/invalidType.out b/python/test-data-2.0/failing/invalidType.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/invalidType.py b/python/test-data-2.0/failing/invalidType.py new file mode 100644 index 00000000..77595170 --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/invalidType2.err b/python/test-data-2.0/failing/invalidType2.err new file mode 100644 index 00000000..bff32582 --- /dev/null +++ b/python/test-data-2.0/failing/invalidType2.err @@ -0,0 +1,4 @@ +Traceback (most recent call last): + File "invalidType2.py", line 5, in + T = Union(list(int), list[float]) +TypeError: 'type' object is not iterable diff --git a/python/test-data-2.0/failing/invalidType2.out b/python/test-data-2.0/failing/invalidType2.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/invalidType2.py b/python/test-data-2.0/failing/invalidType2.py new file mode 100644 index 00000000..1757ecb7 --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/invalidType3.err b/python/test-data-2.0/failing/invalidType3.err new file mode 100644 index 00000000..71e51646 --- /dev/null +++ b/python/test-data-2.0/failing/invalidType3.err @@ -0,0 +1,12 @@ +Traceback (most recent call last): + File "invalidType3.py", line 9, in + foo() + +WyppTypeError: ungültiger Typ `Optional(list(int), list[float])` + +Wolltest du `Optional[list(int), list[float]]` schreiben? + +## Datei invalidType3.py +## Typ deklariert in Zeile 6: + +def foo() -> Optional(list(int), list[float]): diff --git a/python/test-data-2.0/failing/invalidType3.out b/python/test-data-2.0/failing/invalidType3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/invalidType3.py b/python/test-data-2.0/failing/invalidType3.py new file mode 100644 index 00000000..cf8418ba --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/invalidType4.err b/python/test-data-2.0/failing/invalidType4.err new file mode 100644 index 00000000..47b38bcf --- /dev/null +++ b/python/test-data-2.0/failing/invalidType4.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "invalidType4.py", line 9, in + foo() + +WyppTypeError: ungültiger Typ `Optional[list[int], list[float]]` + +## Datei invalidType4.py +## Typ deklariert in Zeile 6: + +def foo() -> Optional[list[int], list[float]]: diff --git a/python/test-data-2.0/failing/invalidType4.out b/python/test-data-2.0/failing/invalidType4.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/invalidType4.py b/python/test-data-2.0/failing/invalidType4.py new file mode 100644 index 00000000..36f511df --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/main.err b/python/test-data-2.0/failing/main.err index 1829924c..a3405fac 100644 --- a/python/test-data-2.0/failing/main.err +++ b/python/test-data-2.0/failing/main.err @@ -1,14 +1,19 @@ Traceback (most recent call last): - File "modules/A/main.py", line 3, in - mod.foo(1) - File "", line 5, in foo + File "main.py", line 4, in + print(mod.foo(1)) + File "mod.py", 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) + +WyppTypeError: 1 + +Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. +Aber der übergebene Wert hat Typ `int`. + +## Datei mod.py +## Fehlerhafter Aufruf in Zeile 5: + + return bar(i) + +## Typ deklariert in Zeile 1: + +def bar(s: str) -> int: diff --git a/python/test-data-2.0/failing/main.py b/python/test-data-2.0/failing/main.py index b089d254..d6036d5d 100644 --- a/python/test-data-2.0/failing/main.py +++ b/python/test-data-2.0/failing/main.py @@ -1,3 +1,4 @@ +# WYPP_TEST_CONFIG: {"args": ["--extra-dir", "test-data-2.0/modules/B"], "pythonPath": "test-data-2.0/modules/B"} import mod print(mod.foo(1)) diff --git a/python/test-data-2.0/failing/testGetSource.err b/python/test-data-2.0/failing/testGetSource.err index 4e0a559f..ee613675 100644 --- a/python/test-data-2.0/failing/testGetSource.err +++ b/python/test-data-2.0/failing/testGetSource.err @@ -1,4 +1,16 @@ Traceback (most recent call last): File "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']? + File "writeYourProgram.py", line 92, in _invalidCall + raise errors.WyppTypeError.invalidType(tyStr, loc) + File "errors.py", line 79, in invalidType + raise WyppTypeError('\n'.join(lines)) + +WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` + +Wolltest du `Literal['klein', 'mittag']` schreiben? + +## Datei testGetSource.py +## Typ deklariert in Zeile 11: + +Art = Literal('klein','mittag') # <= problem is here diff --git a/python/test-data-2.0/failing/testHintParentheses3.err b/python/test-data-2.0/failing/testHintParentheses3.err index 48b0e2b2..6673e629 100644 --- a/python/test-data-2.0/failing/testHintParentheses3.err +++ b/python/test-data-2.0/failing/testHintParentheses3.err @@ -1,10 +1,12 @@ Traceback (most recent call last): - File "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]? + File "testHintParentheses3.py", line 9, in + foo() -def foo() -> Union(list, str): - pass ^^^^^^^^^^^^^^^^ - Did you mean: 'Union[list, str]'? +WyppTypeError: ungültiger Typ `Union(list, str)` -declared at: test-data/testHintParentheses3.py:6 +Wolltest du `Union[list, str]` schreiben? + +## Datei testHintParentheses3.py +## Typ deklariert in Zeile 6: + +def foo() -> Union(list, str): diff --git a/python/test-data-2.0/failing/testLiteral1.err b/python/test-data-2.0/failing/testLiteral1.err index 8789ae0b..ff66d37b 100644 --- a/python/test-data-2.0/failing/testLiteral1.err +++ b/python/test-data-2.0/failing/testLiteral1.err @@ -1,4 +1,16 @@ Traceback (most recent call last): File "testLiteral1.py", line 3, in T = Literal('a', 'b') -untypy.error.WyppTypeError: Cannot call Literal like a function. Did you mean Literal['a', 'b']? + File "writeYourProgram.py", line 92, in _invalidCall + raise errors.WyppTypeError.invalidType(tyStr, loc) + File "errors.py", line 79, in invalidType + raise WyppTypeError('\n'.join(lines)) + +WyppTypeError: ungültiger Typ `Literal('a', 'b')` + +Wolltest du `Literal['a', 'b']` schreiben? + +## Datei testLiteral1.py +## Typ deklariert in Zeile 3: + +T = Literal('a', 'b') diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.err b/python/test-data-2.0/failing/testWrongKeywordArg.err index 500e080c..6af4ac79 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg.err @@ -1,9 +1,12 @@ Traceback (most recent call last): File "testWrongKeywordArg.py", line 4, in foo(x=4) -WyppTypeError: missing a required argument: 'kw' -context: foo(kw: int) -> int -declared at: test-data/testWrongKeywordArg.py:1 -caused by: test-data/testWrongKeywordArg.py:4 - | foo(x=4) +WyppTypeError: unbekanntes Schlüsselwort-Argument + +Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`. + +## Datei testWrongKeywordArg.py +## Fehlerhafter Aufruf in Zeile 4: + +foo(x=4) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 index 2daa463d..70dc7168 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 +++ b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 @@ -1,9 +1,12 @@ Traceback (most recent call last): - File "test-data/testWrongKeywordArg2.py", line 8, in + File "testWrongKeywordArg2.py", line 8, in p = Point(foo=3, y=4) -WyppTypeError: missing a required argument: 'x' -context: record constructor Point(x: int, y: int) -> Self -declared at: test-data/testWrongKeywordArg2.py:3 -caused by: test-data/testWrongKeywordArg2.py:8 - | p = Point(foo=3, y=4) +WyppTypeError: unbekanntes Schlüsselwort-Argument + +Konstruktor des Records `Point` akzeptiert kein Schlüsselwort-Argument `foo`. + +## Datei testWrongKeywordArg2.py +## Fehlerhafter Aufruf in Zeile 8: + +p = Point(foo=3, y=4) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.err b/python/test-data-2.0/failing/testWrongKeywordArg3.err new file mode 100644 index 00000000..8197c40c --- /dev/null +++ b/python/test-data-2.0/failing/testWrongKeywordArg3.err @@ -0,0 +1,12 @@ +Traceback (most recent call last): + File "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 testWrongKeywordArg3.py +## Fehlerhafter Aufruf in Zeile 8: + +p = Point(x=3, y=4, z=4) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.out b/python/test-data-2.0/failing/testWrongKeywordArg3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.py b/python/test-data-2.0/failing/testWrongKeywordArg3.py new file mode 100644 index 00000000..54940e6d --- /dev/null +++ b/python/test-data-2.0/failing/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/test-data-2.0/failing/testWrongKeywordArg4.err b/python/test-data-2.0/failing/testWrongKeywordArg4.err new file mode 100644 index 00000000..eee45903 --- /dev/null +++ b/python/test-data-2.0/failing/testWrongKeywordArg4.err @@ -0,0 +1,12 @@ +Traceback (most recent call last): + File "testWrongKeywordArg4.py", line 4, in + foo(kw=1, x=4) + +WyppTypeError: unbekanntes Schlüsselwort-Argument + +Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`. + +## Datei testWrongKeywordArg4.py +## Fehlerhafter Aufruf in Zeile 4: + +foo(kw=1, x=4) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.out b/python/test-data-2.0/failing/testWrongKeywordArg4.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.py b/python/test-data-2.0/failing/testWrongKeywordArg4.py new file mode 100644 index 00000000..5eae9fd0 --- /dev/null +++ b/python/test-data-2.0/failing/testWrongKeywordArg4.py @@ -0,0 +1,4 @@ +def foo(kw: int) -> int: + return kw + +foo(kw=1, x=4) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.err b/python/test-data-2.0/failing/testWrongKeywordArg5.err new file mode 100644 index 00000000..51aa153f --- /dev/null +++ b/python/test-data-2.0/failing/testWrongKeywordArg5.err @@ -0,0 +1,13 @@ +Traceback (most recent call last): + File "testWrongKeywordArg5.py", line 4, in + foo() + +WyppTypeError: Anzahl der Argument passt nicht + +Funktion `foo` benötigt 1 Argument. +Gegeben: keine Argumente + +## Datei testWrongKeywordArg5.py +## Aufruf in Zeile 4: + +foo() diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.out b/python/test-data-2.0/failing/testWrongKeywordArg5.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.py b/python/test-data-2.0/failing/testWrongKeywordArg5.py new file mode 100644 index 00000000..05c01a30 --- /dev/null +++ b/python/test-data-2.0/failing/testWrongKeywordArg5.py @@ -0,0 +1,4 @@ +def foo(kw: int) -> int: + return kw + +foo() diff --git a/python/test-data-2.0/modules/B/mod.py b/python/test-data-2.0/modules/B/mod.py new file mode 100644 index 00000000..1d7d2978 --- /dev/null +++ b/python/test-data-2.0/modules/B/mod.py @@ -0,0 +1,5 @@ +def bar(s: str) -> int: + return 0 + +def foo(i: int) -> int: + return bar(i) From ea12943d755eff125a932bc95efe49496dacfecc Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 16:59:53 +0200 Subject: [PATCH 43/56] all file tests running --- python/TODO_nowrappers.md | 11 +--- python/fileTestsLib.py | 2 +- python/src/location.py | 5 +- python/src/paths.py | 27 +++++++++ python/src/renderTy.py | 3 + python/src/runner.py | 59 ++++++++++++------- .../failing/testHintParentheses1.err | 2 + .../test-data-2.0/failing/testImpossible.err | 2 +- python/test-data-2.0/failing/testTodo.err | 2 +- .../failing/testTypesProtos3.err | 23 +++++--- .../test-data-2.0/failing/testTypesSet2.err | 22 ------- .../test-data-2.0/failing/testTypesSet2.out | 1 - python/test-data-2.0/failing/testTypesSet2.py | 10 ---- 13 files changed, 94 insertions(+), 75 deletions(-) create mode 100644 python/src/paths.py delete mode 100644 python/test-data-2.0/failing/testTypesSet2.err delete mode 100644 python/test-data-2.0/failing/testTypesSet2.out delete mode 100644 python/test-data-2.0/failing/testTypesSet2.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index a1c47985..9d4c570e 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -5,16 +5,9 @@ * Integration tests * Installation +* Test windows + * Typechecked console * Debug slow startup times * show "@record\nclass C" for record attributes - -Problematic tests: - - test-data-2.0/failing/testTypesProtos3.py # non-static methods without self - - test-data-2.0/failing/testArgs_ok.py # absolute filenames - - - diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index c1234fb8..25b066f7 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -43,7 +43,7 @@ def parseArgs() -> TestOpts: # Parse the arguments args = parser.parse_args() - scriptDir = os.path.dirname(os.path.abspath(__file__)) + scriptDir = os.path.dirname(__file__) return TestOpts( cmd=f'{scriptDir}/src/runYourProgram.py', baseDir=scriptDir, diff --git a/python/src/location.py b/python/src/location.py index 14a198db..12de0a0e 100644 --- a/python/src/location.py +++ b/python/src/location.py @@ -3,7 +3,6 @@ from dataclasses import dataclass import inspect import linecache -from traceback import FrameSummary import dis import ast import ansi @@ -13,6 +12,7 @@ import abc import parsecache from parsecache import FunMatcher +import paths @dataclass class Loc: @@ -22,6 +22,9 @@ class Loc: 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) diff --git a/python/src/paths.py b/python/src/paths.py new file mode 100644 index 00000000..b8dc9161 --- /dev/null +++ b/python/src/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/src/renderTy.py b/python/src/renderTy.py index 75807697..0b337444 100644 --- a/python/src/renderTy.py +++ b/python/src/renderTy.py @@ -27,6 +27,9 @@ def renderTy(tp: Any) -> str: 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) diff --git a/python/src/runner.py b/python/src/runner.py index 8ccc36ae..6737e52a 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -13,13 +13,12 @@ from pathlib import Path import subprocess import runpy -import types from dataclasses import dataclass # local imports import stacktrace import i18n -import typecheck +import paths import instrument from myLogging import * import errors @@ -320,12 +319,11 @@ def runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): if not extraDirs: extraDirs = [] modDir = os.path.dirname(fileToRun) - print(f'fileToRun={fileToRun}, modDir={modDir}') with RunSetup(modDir, [fileToRun] + args): with instrument.setupFinder(modDir, extraDirs, doTypecheck): modName = os.path.basename(os.path.splitext(fileToRun)[0]) - sys.dont_write_bytecode = True # FIXME: remove - runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=True) + sys.dont_write_bytecode = True # FIXME: remove? + runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=False) def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, extraDirs=extraDirs) @@ -379,6 +377,25 @@ def enterInteractive(userDefs): globals()[k] = v print() +_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() @@ -400,7 +417,7 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): if not header: file.write('Traceback (most recent call last):\n') header = True - file.write(x) + file.write(_rewriteFilenameInTraceback(x)) if isWyppError: s = str(val) if s and s[0] != '\n': @@ -505,21 +522,23 @@ def main(globals, argList=None): 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, - doTypecheck=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, doTypecheck=args.checkTypes, - extraDirs=args.extraDirs, loadingFailed=loadingFailed) + with 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 + runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, + doTypecheck=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, doTypecheck=args.checkTypes, + extraDirs=args.extraDirs, loadingFailed=loadingFailed) if isInteractive: enterInteractive(globals) diff --git a/python/test-data-2.0/failing/testHintParentheses1.err b/python/test-data-2.0/failing/testHintParentheses1.err index d5922767..c2c44394 100644 --- a/python/test-data-2.0/failing/testHintParentheses1.err +++ b/python/test-data-2.0/failing/testHintParentheses1.err @@ -4,6 +4,8 @@ Traceback (most recent call last): WyppTypeError: ungültiger Typ `list(int)` +Wolltest du `list[int]` schreiben? + ## Datei testHintParentheses1.py ## Typ deklariert in Zeile 5: diff --git a/python/test-data-2.0/failing/testImpossible.err b/python/test-data-2.0/failing/testImpossible.err index e41ab759..4ef98fd1 100644 --- a/python/test-data-2.0/failing/testImpossible.err +++ b/python/test-data-2.0/failing/testImpossible.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "testImpossible.py", line 3, in impossible() - File "writeYourProgram.py", line 302, in impossible + File "writeYourProgram.py", line 331, in impossible raise errors.ImpossibleError(msg) Das Unmögliche ist passiert! diff --git a/python/test-data-2.0/failing/testTodo.err b/python/test-data-2.0/failing/testTodo.err index b31ff9e1..51ff27af 100644 --- a/python/test-data-2.0/failing/testTodo.err +++ b/python/test-data-2.0/failing/testTodo.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "testTodo.py", line 3, in todo() - File "writeYourProgram.py", line 297, in todo + File "writeYourProgram.py", line 326, in todo raise errors.TodoError(msg) TODO diff --git a/python/test-data-2.0/failing/testTypesProtos3.err b/python/test-data-2.0/failing/testTypesProtos3.err index 397a679d..a9f0bea3 100644 --- a/python/test-data-2.0/failing/testTypesProtos3.err +++ b/python/test-data-2.0/failing/testTypesProtos3.err @@ -1,14 +1,19 @@ Traceback (most recent call last): File "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. + File "testTypesProtos3.py", line 19, in doSomething + print(a.makeSound(3.14)) -given: -expected: value of type Animal +WyppTypeError: -context: doSomething(a: Animal) -> None - ^^^^^^ -declared at: test-data/testTypesProtos3.py:18 -caused by: test-data/testTypesProtos3.py:21 - | doSomething(Dog()) +Der Aufruf der Methode `makeSound` aus Klasse `Dog` erwartet Wert vom Typ `int` als erstes Argument. +Aber der übergebene Wert hat Typ `Dog`. + +## Datei testTypesProtos3.py +## Fehlerhafter Aufruf in Zeile 19: + + print(a.makeSound(3.14)) + +## Typ deklariert in Zeile 13: + + def makeSound(loadness: int) -> str: # self parameter omitted! diff --git a/python/test-data-2.0/failing/testTypesSet2.err b/python/test-data-2.0/failing/testTypesSet2.err deleted file mode 100644 index becc55c0..00000000 --- a/python/test-data-2.0/failing/testTypesSet2.err +++ /dev/null @@ -1,22 +0,0 @@ -Traceback (most recent call last): - File "testTypesSet2.py", line 10, in - foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int - File "testTypesSet2.py", line 7, in foo - return res - -WyppTypeError: [42, '1'] - -Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. - -## Datei testTypesSet2.py -## Rückgabetyp deklariert in Zeile 3: - -def foo(l: set[Callable[[], str]]) -> list[str]: - -## Fehlerhaftes return in Zeile 7: - - return res - -## Aufruf in Zeile 10 verursacht das fehlerhafte return: - -foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int diff --git a/python/test-data-2.0/failing/testTypesSet2.out b/python/test-data-2.0/failing/testTypesSet2.out deleted file mode 100644 index 189744a2..00000000 --- a/python/test-data-2.0/failing/testTypesSet2.out +++ /dev/null @@ -1 +0,0 @@ -['1', '2'] diff --git a/python/test-data-2.0/failing/testTypesSet2.py b/python/test-data-2.0/failing/testTypesSet2.py deleted file mode 100644 index 24579212..00000000 --- a/python/test-data-2.0/failing/testTypesSet2.py +++ /dev/null @@ -1,10 +0,0 @@ -from wypp import * - -def foo(l: set[Callable[[], str]]) -> list[str]: - res = [] - for f in l: - res.append(f()) - return res - -print(sorted(list(foo(set([lambda: "1", lambda: "2"]))))) -foo(set([lambda: "1", lambda: 42])) # error because the 2nd functions returns an int From 4c525d7b6c6451968d6a746f2150e730053c2d6b Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 17:09:04 +0200 Subject: [PATCH 44/56] do not fix file paths in tests --- python/fileTestsLib.py | 12 ++++++++---- python/test-data-2.0/basics/async.err | 14 +++++++------- python/test-data-2.0/basics/async.err_en | 14 +++++++------- python/test-data-2.0/basics/constructor.err | 6 +++--- python/test-data-2.0/basics/constructor.err_en | 6 +++--- python/test-data-2.0/basics/forwardRefs.err | 6 +++--- python/test-data-2.0/basics/forwardRefs.err_en | 6 +++--- python/test-data-2.0/basics/functionArg.err | 6 +++--- python/test-data-2.0/basics/functionArg.err_en | 6 +++--- python/test-data-2.0/basics/functionNoResult.err | 8 ++++---- .../test-data-2.0/basics/functionNoResult.err_en | 8 ++++---- python/test-data-2.0/basics/functionNoResult2.err | 8 ++++---- .../test-data-2.0/basics/functionNoResult2.err_en | 8 ++++---- python/test-data-2.0/basics/functionNoResult3.err | 8 ++++---- .../test-data-2.0/basics/functionNoResult3.err_en | 8 ++++---- python/test-data-2.0/basics/functionResult.err | 8 ++++---- python/test-data-2.0/basics/functionResult.err_en | 8 ++++---- python/test-data-2.0/basics/functionResult2.err | 8 ++++---- python/test-data-2.0/basics/functionResult2.err_en | 8 ++++---- python/test-data-2.0/basics/iterator.err | 8 ++++---- python/test-data-2.0/basics/iterator.err_en | 8 ++++---- python/test-data-2.0/basics/kwargs.err | 6 +++--- python/test-data-2.0/basics/kwargs.err_en | 6 +++--- python/test-data-2.0/basics/kwargs2.err | 6 +++--- python/test-data-2.0/basics/kwargs2.err_en | 6 +++--- python/test-data-2.0/basics/listArg.err | 6 +++--- python/test-data-2.0/basics/listArg.err_en | 6 +++--- python/test-data-2.0/basics/listResult.err | 8 ++++---- python/test-data-2.0/basics/listResult.err_en | 8 ++++---- python/test-data-2.0/basics/method.err | 6 +++--- python/test-data-2.0/basics/method.err_en | 6 +++--- python/test-data-2.0/basics/mutable.err | 6 +++--- python/test-data-2.0/basics/mutable.err_en | 6 +++--- python/test-data-2.0/basics/mutable2.err | 6 +++--- python/test-data-2.0/basics/mutable2.err_en | 6 +++--- python/test-data-2.0/basics/nested.err | 6 +++--- python/test-data-2.0/basics/nestedFun.err | 8 ++++---- python/test-data-2.0/basics/nestedFun.err_en | 8 ++++---- python/test-data-2.0/basics/optionalArgs.err | 6 +++--- python/test-data-2.0/basics/optionalArgs.err_en | 6 +++--- python/test-data-2.0/basics/optionalArgs2.err | 6 +++--- python/test-data-2.0/basics/optionalArgs2.err_en | 6 +++--- python/test-data-2.0/basics/optionalArgs3.err | 6 +++--- python/test-data-2.0/basics/optionalArgs3.err_en | 6 +++--- python/test-data-2.0/basics/optionalArgs4.err | 8 ++++---- python/test-data-2.0/basics/optionalArgs4.err_en | 8 ++++---- python/test-data-2.0/basics/optionalAttr.err | 6 +++--- python/test-data-2.0/basics/optionalAttr.err_en | 6 +++--- python/test-data-2.0/basics/optionalAttr2.err | 6 +++--- python/test-data-2.0/basics/optionalAttr2.err_en | 6 +++--- python/test-data-2.0/basics/optionalAttr3.err | 6 +++--- python/test-data-2.0/basics/optionalAttr3.err_en | 6 +++--- python/test-data-2.0/basics/partial.err | 6 +++--- python/test-data-2.0/basics/partial.err_en | 6 +++--- python/test-data-2.0/basics/record.err | 6 +++--- python/test-data-2.0/basics/record.err_en | 6 +++--- python/test-data-2.0/basics/stack.err | 12 ++++++------ python/test-data-2.0/basics/staticmethod.err | 6 +++--- python/test-data-2.0/basics/staticmethod.err_en | 6 +++--- python/test-data-2.0/basics/testCallable.err | 6 +++--- python/test-data-2.0/basics/tooFewArgs.err | 6 +++--- python/test-data-2.0/basics/tooFewAttrs.err | 6 +++--- python/test-data-2.0/basics/tooManyArgs.err | 6 +++--- python/test-data-2.0/basics/tooManyArgs.err_en | 6 +++--- python/test-data-2.0/basics/tooManyAttrs.err | 6 +++--- python/test-data-2.0/basics/tooManyAttrs.err_en | 6 +++--- python/test-data-2.0/basics/typeAlias.err | 6 +++--- python/test-data-2.0/basics/typeAlias.err_en | 6 +++--- python/test-data-2.0/basics/typeAlias2.err | 6 +++--- python/test-data-2.0/basics/typeAlias2.err_en | 6 +++--- python/test-data-2.0/extras/testTypes2.err | 6 +++--- .../failing/declared-at-missing.err-3.12 | 6 +++--- python/test-data-2.0/failing/invalidType.err | 6 +++--- python/test-data-2.0/failing/invalidType2.err | 4 ++-- python/test-data-2.0/failing/invalidType3.err | 6 +++--- python/test-data-2.0/failing/invalidType4.err | 6 +++--- python/test-data-2.0/failing/main.err | 8 ++++---- python/test-data-2.0/failing/testABCMeta.err | 4 ++-- python/test-data-2.0/failing/testForwardRef2.err | 6 +++--- .../test-data-2.0/failing/testForwardRef4.err-3.12 | 6 +++--- .../test-data-2.0/failing/testForwardRef5.err-3.12 | 6 +++--- python/test-data-2.0/failing/testGetSource.err | 10 +++++----- .../test-data-2.0/failing/testHintParentheses1.err | 6 +++--- .../test-data-2.0/failing/testHintParentheses2.err | 6 +++--- .../test-data-2.0/failing/testHintParentheses3.err | 6 +++--- python/test-data-2.0/failing/testImpossible.err | 6 +++--- python/test-data-2.0/failing/testIndexError.err | 6 +++--- .../test-data-2.0/failing/testInvalidLiteral.err | 6 +++--- .../failing/testIterableImplicitAny.err | 6 +++--- python/test-data-2.0/failing/testLiteral1.err | 10 +++++----- python/test-data-2.0/failing/testLockFactory.err | 6 +++--- python/test-data-2.0/failing/testLockFactory2.err | 6 +++--- python/test-data-2.0/failing/testMissingReturn.err | 8 ++++---- .../failing/testRecordSetTypeForwardRef.err-3.12 | 8 ++++---- .../failing/testRecordSetTypes.err-3.12 | 8 ++++---- .../test-data-2.0/failing/testRecordTypes.err-3.12 | 6 +++--- python/test-data-2.0/failing/testTodo.err | 6 +++--- python/test-data-2.0/failing/testTraceback.err | 6 +++--- python/test-data-2.0/failing/testTraceback2.err | 4 ++-- python/test-data-2.0/failing/testTraceback3.err | 4 ++-- python/test-data-2.0/failing/testTypes1.err | 6 +++--- python/test-data-2.0/failing/testTypesDict3.err | 8 ++++---- python/test-data-2.0/failing/testTypesDict4.err | 10 +++++----- .../failing/testTypesHigherOrderFuns.err | 8 ++++---- .../failing/testTypesHigherOrderFuns3.err | 6 +++--- python/test-data-2.0/failing/testTypesProtos1.err | 8 ++++---- python/test-data-2.0/failing/testTypesProtos2.err | 8 ++++---- python/test-data-2.0/failing/testTypesProtos3.err | 8 ++++---- python/test-data-2.0/failing/testTypesProtos4.err | 10 +++++----- python/test-data-2.0/failing/testTypesProtos6.err | 14 +++++++------- python/test-data-2.0/failing/testTypesProtos7.err | 14 +++++++------- python/test-data-2.0/failing/testTypesProtos8.err | 8 ++++---- python/test-data-2.0/failing/testTypesProtos9.err | 8 ++++---- .../failing/testTypesRecordInheritance.err-3.12 | 6 +++--- python/test-data-2.0/failing/testTypesReturn.err | 8 ++++---- .../test-data-2.0/failing/testTypesSequence1.err | 6 +++--- .../test-data-2.0/failing/testTypesSequence2.err | 6 +++--- .../failing/testTypesSubclassing1.err | 8 ++++---- python/test-data-2.0/failing/testTypesTuple1.err | 6 +++--- .../test-data-2.0/failing/testWrongKeywordArg.err | 6 +++--- .../failing/testWrongKeywordArg2.err-3.12 | 6 +++--- .../test-data-2.0/failing/testWrongKeywordArg3.err | 6 +++--- .../test-data-2.0/failing/testWrongKeywordArg4.err | 6 +++--- .../test-data-2.0/failing/testWrongKeywordArg5.err | 6 +++--- .../failing/testWrongNumOfArguments.err | 6 +++--- .../failing/testWrongNumOfArguments2.err | 6 +++--- python/test-data-2.0/failing/wrong-caused-by.err | 6 +++--- 127 files changed, 440 insertions(+), 436 deletions(-) diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 25b066f7..47778690 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -13,6 +13,8 @@ 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 @@ -156,7 +158,12 @@ def checkOutputOk(testFile: str, outputType: str, expectedFile: str, actualFile: if expected != actual: print(f"Test {testFile} {outputType} output mismatch:") subprocess.run(['diff', '-u', expectedFile, actualFile]) - return False + if GLOBAL_RECORD_ALL: + with open(expectedFile, 'w') as f: + f.write(actual) + return True + else: + return False else: return True @@ -195,9 +202,6 @@ def fixOutput(filePath: str): """ 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 "[^"]*/([^/"]*)"', 'File "\\1"', content, flags=re.MULTILINE) # Remove file paths - content = re.sub(r'## File .*/([^/]*)$', '## File \\1', content, flags=re.MULTILINE) - content = re.sub(r'## Datei .*/([^/]*)$', '## Datei \\1', content, flags=re.MULTILINE) with open(filePath, 'w') as f: f.write(content) diff --git a/python/test-data-2.0/basics/async.err b/python/test-data-2.0/basics/async.err index 3db47e6d..1feb6b7f 100644 --- a/python/test-data-2.0/basics/async.err +++ b/python/test-data-2.0/basics/async.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "async.py", line 10, in + File "test-data-2.0/basics/async.py", line 10, in asyncio.run(main()) - File "runners.py", line 194, in run + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run return runner.run(main) - File "runners.py", line 129, in run + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 129, in run signal.signal(signal.SIGINT, signal.default_int_handler) - File "base_events.py", line 684, in run_until_complete + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete return future.result() - File "async.py", line 7, in main + File "test-data-2.0/basics/async.py", line 7, in main x = await foo("blub") WyppTypeError: "blub" @@ -15,11 +15,11 @@ WyppTypeError: "blub" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei async.py +## Datei test-data-2.0/basics/async.py ## Fehlerhafter Aufruf in Zeile 7: x = await foo("blub") ## Typ deklariert in Zeile 3: -async def foo(i: int): +async def foo(i: int): \ No newline at end of file diff --git a/python/test-data-2.0/basics/async.err_en b/python/test-data-2.0/basics/async.err_en index 5933f2b3..62be50a7 100644 --- a/python/test-data-2.0/basics/async.err_en +++ b/python/test-data-2.0/basics/async.err_en @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "async.py", line 10, in + File "test-data-2.0/basics/async.py", line 10, in asyncio.run(main()) - File "runners.py", line 194, in run + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run return runner.run(main) - File "runners.py", line 129, in run + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 129, in run signal.signal(signal.SIGINT, signal.default_int_handler) - File "base_events.py", line 684, in run_until_complete + File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete return future.result() - File "async.py", line 7, in main + File "test-data-2.0/basics/async.py", line 7, in main x = await foo("blub") WyppTypeError: "blub" @@ -15,11 +15,11 @@ WyppTypeError: "blub" The call of function `foo` expects value of type `int` as 1st argument. But the value given has type `str`. -## File async.py +## File test-data-2.0/basics/async.py ## Problematic call in line 7: x = await foo("blub") ## Type declared in line 3: -async def foo(i: int): +async def foo(i: int): \ No newline at end of file diff --git a/python/test-data-2.0/basics/constructor.err b/python/test-data-2.0/basics/constructor.err index 471fe111..41684388 100644 --- a/python/test-data-2.0/basics/constructor.err +++ b/python/test-data-2.0/basics/constructor.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "constructor.py", line 5, in + File "test-data-2.0/basics/constructor.py", line 5, in c = C("1") WyppTypeError: "1" @@ -7,11 +7,11 @@ 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 constructor.py +## Datei test-data-2.0/basics/constructor.py ## Fehlerhafter Aufruf in Zeile 5: c = C("1") ## Typ deklariert in Zeile 2: - def __init__(self, x: int): + def __init__(self, x: int): \ No newline at end of file diff --git a/python/test-data-2.0/basics/constructor.err_en b/python/test-data-2.0/basics/constructor.err_en index ed55f998..defa90af 100644 --- a/python/test-data-2.0/basics/constructor.err_en +++ b/python/test-data-2.0/basics/constructor.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "constructor.py", line 5, in + File "test-data-2.0/basics/constructor.py", line 5, in c = C("1") WyppTypeError: "1" @@ -7,11 +7,11 @@ 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 constructor.py +## File test-data-2.0/basics/constructor.py ## Problematic call in line 5: c = C("1") ## Type declared in line 2: - def __init__(self, x: int): + def __init__(self, x: int): \ No newline at end of file diff --git a/python/test-data-2.0/basics/forwardRefs.err b/python/test-data-2.0/basics/forwardRefs.err index 0c5aa87b..20150132 100644 --- a/python/test-data-2.0/basics/forwardRefs.err +++ b/python/test-data-2.0/basics/forwardRefs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "forwardRefs.py", line 12, in + File "test-data-2.0/basics/forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> @@ -7,11 +7,11 @@ 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 forwardRefs.py +## Datei test-data-2.0/basics/forwardRefs.py ## Fehlerhafter Aufruf in Zeile 12: a.foo(a) ## Typ deklariert in Zeile 4: - def foo(self, b: B): + def foo(self, b: B): \ No newline at end of file diff --git a/python/test-data-2.0/basics/forwardRefs.err_en b/python/test-data-2.0/basics/forwardRefs.err_en index 91cd288f..5172c0dd 100644 --- a/python/test-data-2.0/basics/forwardRefs.err_en +++ b/python/test-data-2.0/basics/forwardRefs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "forwardRefs.py", line 12, in + File "test-data-2.0/basics/forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> @@ -7,11 +7,11 @@ 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 forwardRefs.py +## File test-data-2.0/basics/forwardRefs.py ## Problematic call in line 12: a.foo(a) ## Type declared in line 4: - def foo(self, b: B): + def foo(self, b: B): \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionArg.err b/python/test-data-2.0/basics/functionArg.err index 6f4dd723..960c3a68 100644 --- a/python/test-data-2.0/basics/functionArg.err +++ b/python/test-data-2.0/basics/functionArg.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "functionArg.py", line 8, in + File "test-data-2.0/basics/functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" @@ -7,11 +7,11 @@ WyppTypeError: "1" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als zweites Argument. Aber der übergebene Wert hat Typ `str`. -## Datei functionArg.py +## Datei test-data-2.0/basics/functionArg.py ## Fehlerhafter Aufruf in Zeile 8: print(foo(foo(1, "1"), 42)) ## Typ deklariert in Zeile 4: -def foo(i: int, j: int) -> int: +def foo(i: int, j: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionArg.err_en b/python/test-data-2.0/basics/functionArg.err_en index 37416254..f05a410e 100644 --- a/python/test-data-2.0/basics/functionArg.err_en +++ b/python/test-data-2.0/basics/functionArg.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "functionArg.py", line 8, in + File "test-data-2.0/basics/functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" @@ -7,11 +7,11 @@ WyppTypeError: "1" The call of function `foo` expects value of type `int` as 2nd argument. But the value given has type `str`. -## File functionArg.py +## File test-data-2.0/basics/functionArg.py ## Problematic call in line 8: print(foo(foo(1, "1"), 42)) ## Type declared in line 4: -def foo(i: int, j: int) -> int: +def foo(i: int, j: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult.err b/python/test-data-2.0/basics/functionNoResult.err index 3e5e7bf0..108d26f4 100644 --- a/python/test-data-2.0/basics/functionNoResult.err +++ b/python/test-data-2.0/basics/functionNoResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult.py", line 4, in + File "test-data-2.0/basics/functionNoResult.py", line 4, in foo(1) - File "functionNoResult.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei functionNoResult.py +## Datei test-data-2.0/basics/functionNoResult.py ## Rückgabetyp deklariert in Zeile 1: def foo(i: int) -> None: @@ -20,4 +20,4 @@ def foo(i: int) -> None: ## Aufruf in Zeile 4 verursacht das fehlerhafte return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult.err_en b/python/test-data-2.0/basics/functionNoResult.err_en index 18983e58..01ad43c9 100644 --- a/python/test-data-2.0/basics/functionNoResult.err_en +++ b/python/test-data-2.0/basics/functionNoResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult.py", line 4, in + File "test-data-2.0/basics/functionNoResult.py", line 4, in foo(1) - File "functionNoResult.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File functionNoResult.py +## File test-data-2.0/basics/functionNoResult.py ## Result type declared in line 1: def foo(i: int) -> None: @@ -20,4 +20,4 @@ def foo(i: int) -> None: ## Call in line 4 causes the problematic return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult2.err b/python/test-data-2.0/basics/functionNoResult2.err index c8c6bb46..1f225026 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err +++ b/python/test-data-2.0/basics/functionNoResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult2.py", line 4, in + File "test-data-2.0/basics/functionNoResult2.py", line 4, in foo(1) - File "functionNoResult2.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult2.py", line 2, in foo pass WyppTypeError: kein Rückgabewert vorhanden @@ -9,11 +9,11 @@ WyppTypeError: kein Rückgabewert vorhanden Rückgabewert vom Typ `int` erwartet bei Aufruf der Funktion `foo`. Aber kein Rückgabewert vorhanden. -## Datei functionNoResult2.py +## Datei test-data-2.0/basics/functionNoResult2.py ## Rückgabetyp deklariert in Zeile 1: def foo(i: int) -> int: ## Aufruf in Zeile 4 führt dazu, dass die Funktion keinen Wert zurückgibt: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult2.err_en b/python/test-data-2.0/basics/functionNoResult2.err_en index 5bfdd5c5..5419d75c 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err_en +++ b/python/test-data-2.0/basics/functionNoResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult2.py", line 4, in + File "test-data-2.0/basics/functionNoResult2.py", line 4, in foo(1) - File "functionNoResult2.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult2.py", line 2, in foo pass WyppTypeError: no result returned @@ -9,11 +9,11 @@ WyppTypeError: no result returned Expecting return value of type `int` when calling function `foo`. But no value returned. -## File functionNoResult2.py +## File test-data-2.0/basics/functionNoResult2.py ## Result type declared in line 1: def foo(i: int) -> int: ## Call in line 4 causes the function to return no value: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult3.err b/python/test-data-2.0/basics/functionNoResult3.err index c78ac3b2..6b8a331c 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err +++ b/python/test-data-2.0/basics/functionNoResult3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult3.py", line 4, in + File "test-data-2.0/basics/functionNoResult3.py", line 4, in foo(1) - File "functionNoResult3.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,11 +9,11 @@ WyppTypeError: "x" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei functionNoResult3.py +## Datei test-data-2.0/basics/functionNoResult3.py ## Fehlerhaftes return in Zeile 2: return "x" ## Aufruf in Zeile 4 verursacht das fehlerhafte return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionNoResult3.err_en b/python/test-data-2.0/basics/functionNoResult3.err_en index accc5607..84374e36 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err_en +++ b/python/test-data-2.0/basics/functionNoResult3.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionNoResult3.py", line 4, in + File "test-data-2.0/basics/functionNoResult3.py", line 4, in foo(1) - File "functionNoResult3.py", line 2, in foo + File "test-data-2.0/basics/functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,11 +9,11 @@ WyppTypeError: "x" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File functionNoResult3.py +## File test-data-2.0/basics/functionNoResult3.py ## Problematic return in line 2: return "x" ## Call in line 4 causes the problematic return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionResult.err b/python/test-data-2.0/basics/functionResult.err index 15c578fa..4715f728 100644 --- a/python/test-data-2.0/basics/functionResult.err +++ b/python/test-data-2.0/basics/functionResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionResult.py", line 7, in + File "test-data-2.0/basics/functionResult.py", line 7, in foo(1) - File "functionResult.py", line 5, in foo + File "test-data-2.0/basics/functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ 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 functionResult.py +## Datei test-data-2.0/basics/functionResult.py ## Rückgabetyp deklariert in Zeile 4: def foo(i: int) -> int: @@ -20,4 +20,4 @@ def foo(i: int) -> int: ## Aufruf in Zeile 7 verursacht das fehlerhafte return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionResult.err_en b/python/test-data-2.0/basics/functionResult.err_en index ee464109..5ae4c3d7 100644 --- a/python/test-data-2.0/basics/functionResult.err_en +++ b/python/test-data-2.0/basics/functionResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionResult.py", line 7, in + File "test-data-2.0/basics/functionResult.py", line 7, in foo(1) - File "functionResult.py", line 5, in foo + File "test-data-2.0/basics/functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ WyppTypeError: "foo_1" Expecting return value of type `int` when calling function `foo`. But the call returns a value of type `str`. -## File functionResult.py +## File test-data-2.0/basics/functionResult.py ## Result type declared in line 4: def foo(i: int) -> int: @@ -20,4 +20,4 @@ def foo(i: int) -> int: ## Call in line 7 causes the problematic return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionResult2.err b/python/test-data-2.0/basics/functionResult2.err index be536d67..9d3cc233 100644 --- a/python/test-data-2.0/basics/functionResult2.err +++ b/python/test-data-2.0/basics/functionResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionResult2.py", line 7, in + File "test-data-2.0/basics/functionResult2.py", line 7, in foo(1) - File "functionResult2.py", line 5, in foo + File "test-data-2.0/basics/functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,11 +9,11 @@ WyppTypeError: "foo_1" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei functionResult2.py +## Datei test-data-2.0/basics/functionResult2.py ## Fehlerhaftes return in Zeile 5: return "foo_" + str(i) ## Aufruf in Zeile 7 verursacht das fehlerhafte return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/functionResult2.err_en b/python/test-data-2.0/basics/functionResult2.err_en index 766d5029..d96d8fa8 100644 --- a/python/test-data-2.0/basics/functionResult2.err_en +++ b/python/test-data-2.0/basics/functionResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "functionResult2.py", line 7, in + File "test-data-2.0/basics/functionResult2.py", line 7, in foo(1) - File "functionResult2.py", line 5, in foo + File "test-data-2.0/basics/functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,11 +9,11 @@ WyppTypeError: "foo_1" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File functionResult2.py +## File test-data-2.0/basics/functionResult2.py ## Problematic return in line 5: return "foo_" + str(i) ## Call in line 7 causes the problematic return: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/iterator.err b/python/test-data-2.0/basics/iterator.err index 196e9e46..6332ba8c 100644 --- a/python/test-data-2.0/basics/iterator.err +++ b/python/test-data-2.0/basics/iterator.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "iterator.py", line 6, in + File "test-data-2.0/basics/iterator.py", line 6, in g = my_generator() - File "iterator.py", line 4, in my_generator + File "test-data-2.0/basics/iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 @@ -9,7 +9,7 @@ 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 iterator.py +## Datei test-data-2.0/basics/iterator.py ## Rückgabetyp deklariert in Zeile 3: def my_generator() -> Iterator[int]: @@ -20,4 +20,4 @@ def my_generator() -> Iterator[int]: ## Aufruf in Zeile 6 verursacht das fehlerhafte return: -g = my_generator() +g = my_generator() \ No newline at end of file diff --git a/python/test-data-2.0/basics/iterator.err_en b/python/test-data-2.0/basics/iterator.err_en index 77f9b078..019d06ef 100644 --- a/python/test-data-2.0/basics/iterator.err_en +++ b/python/test-data-2.0/basics/iterator.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "iterator.py", line 6, in + File "test-data-2.0/basics/iterator.py", line 6, in g = my_generator() - File "iterator.py", line 4, in my_generator + File "test-data-2.0/basics/iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 @@ -9,7 +9,7 @@ WyppTypeError: 1 Expecting return value of type `Iterator[int]` when calling function `my_generator`. But the call returns a value of type `int`. -## File iterator.py +## File test-data-2.0/basics/iterator.py ## Result type declared in line 3: def my_generator() -> Iterator[int]: @@ -20,4 +20,4 @@ def my_generator() -> Iterator[int]: ## Call in line 6 causes the problematic return: -g = my_generator() +g = my_generator() \ No newline at end of file diff --git a/python/test-data-2.0/basics/kwargs.err b/python/test-data-2.0/basics/kwargs.err index b05b1527..30f46a06 100644 --- a/python/test-data-2.0/basics/kwargs.err +++ b/python/test-data-2.0/basics/kwargs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "kwargs.py", line 4, in + File "test-data-2.0/basics/kwargs.py", line 4, in foo(1, y=1, z=2) WyppTypeError: 2 @@ -7,11 +7,11 @@ WyppTypeError: 2 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`. Aber der übergebene Wert hat Typ `int`. -## Datei kwargs.py +## Datei test-data-2.0/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, z: str) -> int: +def foo(x: int, y: int, z: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/kwargs.err_en b/python/test-data-2.0/basics/kwargs.err_en index 99964074..00c0cebb 100644 --- a/python/test-data-2.0/basics/kwargs.err_en +++ b/python/test-data-2.0/basics/kwargs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "kwargs.py", line 4, in + File "test-data-2.0/basics/kwargs.py", line 4, in foo(1, y=1, z=2) WyppTypeError: 2 @@ -7,11 +7,11 @@ WyppTypeError: 2 The call of function `foo` expects value of type `str` as argument `z`. But the value given has type `int`. -## File kwargs.py +## File test-data-2.0/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, z: str) -> int: +def foo(x: int, y: int, z: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/kwargs2.err b/python/test-data-2.0/basics/kwargs2.err index 64ad6240..943d6037 100644 --- a/python/test-data-2.0/basics/kwargs2.err +++ b/python/test-data-2.0/basics/kwargs2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "kwargs2.py", line 4, in + File "test-data-2.0/basics/kwargs2.py", line 4, in foo(1, z=2, y='foo') WyppTypeError: 2 @@ -7,11 +7,11 @@ WyppTypeError: 2 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`. Aber der übergebene Wert hat Typ `int`. -## Datei kwargs2.py +## Datei test-data-2.0/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, z: str) -> int: +def foo(x: int, y: int, z: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/kwargs2.err_en b/python/test-data-2.0/basics/kwargs2.err_en index 911a68dd..2c71edf8 100644 --- a/python/test-data-2.0/basics/kwargs2.err_en +++ b/python/test-data-2.0/basics/kwargs2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "kwargs2.py", line 4, in + File "test-data-2.0/basics/kwargs2.py", line 4, in foo(1, z=2, y='foo') WyppTypeError: 2 @@ -7,11 +7,11 @@ WyppTypeError: 2 The call of function `foo` expects value of type `str` as argument `z`. But the value given has type `int`. -## File kwargs2.py +## File test-data-2.0/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, z: str) -> int: +def foo(x: int, y: int, z: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/listArg.err b/python/test-data-2.0/basics/listArg.err index 9e6500e0..e2c57b57 100644 --- a/python/test-data-2.0/basics/listArg.err +++ b/python/test-data-2.0/basics/listArg.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "listArg.py", line 4, in + File "test-data-2.0/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 listArg.py +## Datei test-data-2.0/basics/listArg.py ## Fehlerhafter Aufruf in Zeile 4: foo([1, 2, "3"]) ## Typ deklariert in Zeile 1: -def foo(l: list[int]) -> int: +def foo(l: list[int]) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/listArg.err_en b/python/test-data-2.0/basics/listArg.err_en index f77bc492..d3ed3cef 100644 --- a/python/test-data-2.0/basics/listArg.err_en +++ b/python/test-data-2.0/basics/listArg.err_en @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "listArg.py", line 4, in + File "test-data-2.0/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 listArg.py +## File test-data-2.0/basics/listArg.py ## Problematic call in line 4: foo([1, 2, "3"]) ## Type declared in line 1: -def foo(l: list[int]) -> int: +def foo(l: list[int]) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/listResult.err b/python/test-data-2.0/basics/listResult.err index ba70d720..adef9ecb 100644 --- a/python/test-data-2.0/basics/listResult.err +++ b/python/test-data-2.0/basics/listResult.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "listResult.py", line 5, in + File "test-data-2.0/basics/listResult.py", line 5, in foo([1, 2, 3]) - File "listResult.py", line 3, in foo + File "test-data-2.0/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 listResult.py +## Datei test-data-2.0/basics/listResult.py ## Rückgabetyp deklariert in Zeile 1: def foo(l: list[int]) -> list[int]: @@ -19,4 +19,4 @@ def foo(l: list[int]) -> list[int]: ## Aufruf in Zeile 5 verursacht das fehlerhafte return: -foo([1, 2, 3]) +foo([1, 2, 3]) \ No newline at end of file diff --git a/python/test-data-2.0/basics/listResult.err_en b/python/test-data-2.0/basics/listResult.err_en index 36644de2..62840e5a 100644 --- a/python/test-data-2.0/basics/listResult.err_en +++ b/python/test-data-2.0/basics/listResult.err_en @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "listResult.py", line 5, in + File "test-data-2.0/basics/listResult.py", line 5, in foo([1, 2, 3]) - File "listResult.py", line 3, in foo + File "test-data-2.0/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 listResult.py +## File test-data-2.0/basics/listResult.py ## Result type declared in line 1: def foo(l: list[int]) -> list[int]: @@ -19,4 +19,4 @@ def foo(l: list[int]) -> list[int]: ## Call in line 5 causes the problematic return: -foo([1, 2, 3]) +foo([1, 2, 3]) \ No newline at end of file diff --git a/python/test-data-2.0/basics/method.err b/python/test-data-2.0/basics/method.err index 66b1b7c4..3318d9e5 100644 --- a/python/test-data-2.0/basics/method.err +++ b/python/test-data-2.0/basics/method.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "method.py", line 9, in + File "test-data-2.0/basics/method.py", line 9, in c.method("2") WyppTypeError: "2" @@ -7,11 +7,11 @@ 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 method.py +## Datei test-data-2.0/basics/method.py ## Fehlerhafter Aufruf in Zeile 9: c.method("2") ## Typ deklariert in Zeile 5: - def method(self, y: int) -> int: + def method(self, y: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/method.err_en b/python/test-data-2.0/basics/method.err_en index 9c391898..f0401824 100644 --- a/python/test-data-2.0/basics/method.err_en +++ b/python/test-data-2.0/basics/method.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "method.py", line 9, in + File "test-data-2.0/basics/method.py", line 9, in c.method("2") WyppTypeError: "2" @@ -7,11 +7,11 @@ 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 method.py +## File test-data-2.0/basics/method.py ## Problematic call in line 9: c.method("2") ## Type declared in line 5: - def method(self, y: int) -> int: + def method(self, y: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/mutable.err b/python/test-data-2.0/basics/mutable.err index 3034ff86..52e96246 100644 --- a/python/test-data-2.0/basics/mutable.err +++ b/python/test-data-2.0/basics/mutable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "mutable.py", line 10, in + File "test-data-2.0/basics/mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" @@ -7,11 +7,11 @@ WyppTypeError: "foo" Attribut `y` des Records `Point` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei mutable.py +## Datei test-data-2.0/basics/mutable.py ## Fehlerhafte Zuweisung in Zeile 10: p.y = "foo" ## Typ deklariert in Zeile 6: - y: int + y: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/mutable.err_en b/python/test-data-2.0/basics/mutable.err_en index 6ed27a1d..2c1a97f7 100644 --- a/python/test-data-2.0/basics/mutable.err_en +++ b/python/test-data-2.0/basics/mutable.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "mutable.py", line 10, in + File "test-data-2.0/basics/mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" @@ -7,11 +7,11 @@ WyppTypeError: "foo" Attribute `y` of record `Point` declared with type `int.` Cannot set attribute to value of type `str`. -## File mutable.py +## File test-data-2.0/basics/mutable.py ## Problematic assignment in line 10: p.y = "foo" ## Type declared in line 6: - y: int + y: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/mutable2.err b/python/test-data-2.0/basics/mutable2.err index 15d4622b..53f2453c 100644 --- a/python/test-data-2.0/basics/mutable2.err +++ b/python/test-data-2.0/basics/mutable2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "mutable2.py", line 8, in + File "test-data-2.0/basics/mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' @@ -7,11 +7,11 @@ 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 mutable2.py +## Datei test-data-2.0/basics/mutable2.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(1, '2') ## Typ deklariert in Zeile 6: - y: int + y: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/mutable2.err_en b/python/test-data-2.0/basics/mutable2.err_en index 93ef603a..ef2272ab 100644 --- a/python/test-data-2.0/basics/mutable2.err_en +++ b/python/test-data-2.0/basics/mutable2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "mutable2.py", line 8, in + File "test-data-2.0/basics/mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' @@ -7,11 +7,11 @@ 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 mutable2.py +## File test-data-2.0/basics/mutable2.py ## Problematic call in line 8: p = Point(1, '2') ## Type declared in line 6: - y: int + y: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/nested.err b/python/test-data-2.0/basics/nested.err index 732455ab..60ad4340 100644 --- a/python/test-data-2.0/basics/nested.err +++ b/python/test-data-2.0/basics/nested.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "nested.py", line 4, in + File "test-data-2.0/basics/nested.py", line 4, in foo(42) WyppTypeError: 42 @@ -7,11 +7,11 @@ 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 nested.py +## Datei test-data-2.0/basics/nested.py ## Fehlerhafter Aufruf in Zeile 4: foo(42) ## Typ deklariert in Zeile 1: -def foo(l: list[list[int]]) -> int: +def foo(l: list[list[int]]) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/nestedFun.err b/python/test-data-2.0/basics/nestedFun.err index 1bd7d166..b70bcf7f 100644 --- a/python/test-data-2.0/basics/nestedFun.err +++ b/python/test-data-2.0/basics/nestedFun.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "nestedFun.py", line 9, in + File "test-data-2.0/basics/nestedFun.py", line 9, in foo(1) - File "nestedFun.py", line 7, in foo + File "test-data-2.0/basics/nestedFun.py", line 7, in foo return bar("foo") WyppTypeError: "foo" @@ -9,11 +9,11 @@ WyppTypeError: "foo" Der Aufruf der Funktion `bar` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei nestedFun.py +## Datei test-data-2.0/basics/nestedFun.py ## Fehlerhafter Aufruf in Zeile 7: return bar("foo") ## Typ deklariert in Zeile 5: - def bar(j: int) -> int: + def bar(j: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/nestedFun.err_en b/python/test-data-2.0/basics/nestedFun.err_en index 11d5323b..fafacf51 100644 --- a/python/test-data-2.0/basics/nestedFun.err_en +++ b/python/test-data-2.0/basics/nestedFun.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "nestedFun.py", line 9, in + File "test-data-2.0/basics/nestedFun.py", line 9, in foo(1) - File "nestedFun.py", line 7, in foo + File "test-data-2.0/basics/nestedFun.py", line 7, in foo return bar("foo") WyppTypeError: "foo" @@ -9,11 +9,11 @@ WyppTypeError: "foo" The call of function `bar` expects value of type `int` as 1st argument. But the value given has type `str`. -## File nestedFun.py +## File test-data-2.0/basics/nestedFun.py ## Problematic call in line 7: return bar("foo") ## Type declared in line 5: - def bar(j: int) -> int: + def bar(j: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs.err b/python/test-data-2.0/basics/optionalArgs.err index b40f2c3f..78d5ec45 100644 --- a/python/test-data-2.0/basics/optionalArgs.err +++ b/python/test-data-2.0/basics/optionalArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs.py", line 1, in + File "test-data-2.0/basics/optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Default-Wert des Parameters `s` der Funktion `foo` muss vom Typ `str` sein. Aber der Default-Wert hat Typ `int`. -## Datei optionalArgs.py +## Datei test-data-2.0/basics/optionalArgs.py ## Parameter deklariert in Zeile 1: -def foo(i: int, s: str=2): +def foo(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs.err_en b/python/test-data-2.0/basics/optionalArgs.err_en index 51577da9..f13bf117 100644 --- a/python/test-data-2.0/basics/optionalArgs.err_en +++ b/python/test-data-2.0/basics/optionalArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs.py", line 1, in + File "test-data-2.0/basics/optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Default value for parameter `s` of function `foo` must have type `str`. But the default value has type `int`. -## File optionalArgs.py +## File test-data-2.0/basics/optionalArgs.py ## Parameter declared in line 1: -def foo(i: int, s: str=2): +def foo(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs2.err b/python/test-data-2.0/basics/optionalArgs2.err index 3351d664..f7088044 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err +++ b/python/test-data-2.0/basics/optionalArgs2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs2.py", line 4, in + File "test-data-2.0/basics/optionalArgs2.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt mindestens 2 Argumente. Gegeben: 1 Argument -## Datei optionalArgs2.py +## Datei test-data-2.0/basics/optionalArgs2.py ## Aufruf in Zeile 4: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs2.err_en b/python/test-data-2.0/basics/optionalArgs2.err_en index 083811a2..3b1f6165 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err_en +++ b/python/test-data-2.0/basics/optionalArgs2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs2.py", line 4, in + File "test-data-2.0/basics/optionalArgs2.py", line 4, in foo(1) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes at least 2 arguments. Given: 1 argument -## File optionalArgs2.py +## File test-data-2.0/basics/optionalArgs2.py ## Call in line 4: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs3.err b/python/test-data-2.0/basics/optionalArgs3.err index d3660853..d67e0af0 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err +++ b/python/test-data-2.0/basics/optionalArgs3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs3.py", line 4, in + File "test-data-2.0/basics/optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` akzeptiert höchstens 3 Argumente. Gegeben: 4 Argumente -## Datei optionalArgs3.py +## Datei test-data-2.0/basics/optionalArgs3.py ## Aufruf in Zeile 4: -foo(1, 2, 3, 4) +foo(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs3.err_en b/python/test-data-2.0/basics/optionalArgs3.err_en index 6f82f25d..4c5a51ae 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err_en +++ b/python/test-data-2.0/basics/optionalArgs3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalArgs3.py", line 4, in + File "test-data-2.0/basics/optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes at most 3 arguments. Given: 4 arguments -## File optionalArgs3.py +## File test-data-2.0/basics/optionalArgs3.py ## Call in line 4: -foo(1, 2, 3, 4) +foo(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs4.err b/python/test-data-2.0/basics/optionalArgs4.err index 7dd68ed7..6f558311 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err +++ b/python/test-data-2.0/basics/optionalArgs4.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "optionalArgs4.py", line 1, in + File "test-data-2.0/basics/optionalArgs4.py", line 1, in class C: - File "optionalArgs4.py", line 2, in C + File "test-data-2.0/basics/optionalArgs4.py", line 2, in C def __init__(i: int, s: str=2): WyppTypeError: 2 @@ -9,7 +9,7 @@ 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 optionalArgs4.py +## Datei test-data-2.0/basics/optionalArgs4.py ## Parameter deklariert in Zeile 2: - def __init__(i: int, s: str=2): + def __init__(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs4.err_en b/python/test-data-2.0/basics/optionalArgs4.err_en index 1c073588..2a28d988 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err_en +++ b/python/test-data-2.0/basics/optionalArgs4.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "optionalArgs4.py", line 1, in + File "test-data-2.0/basics/optionalArgs4.py", line 1, in class C: - File "optionalArgs4.py", line 2, in C + File "test-data-2.0/basics/optionalArgs4.py", line 2, in C def __init__(i: int, s: str=2): WyppTypeError: 2 @@ -9,7 +9,7 @@ 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 optionalArgs4.py +## File test-data-2.0/basics/optionalArgs4.py ## Parameter declared in line 2: - def __init__(i: int, s: str=2): + def __init__(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr.err b/python/test-data-2.0/basics/optionalAttr.err index 8f4bfa83..33b11423 100644 --- a/python/test-data-2.0/basics/optionalAttr.err +++ b/python/test-data-2.0/basics/optionalAttr.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr.py", line 3, in + File "test-data-2.0/basics/optionalAttr.py", line 3, in @record WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' Default-Wert des Attributs `y` des Records `C` muss vom Typ `int` sein. Aber der Default-Wert hat Typ `str`. -## Datei optionalAttr.py +## Datei test-data-2.0/basics/optionalAttr.py ## Parameter deklariert in Zeile 6: - y: int = 'foo' + y: int = 'foo' \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr.err_en b/python/test-data-2.0/basics/optionalAttr.err_en index a8b6f0d0..de6543dd 100644 --- a/python/test-data-2.0/basics/optionalAttr.err_en +++ b/python/test-data-2.0/basics/optionalAttr.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr.py", line 3, in + File "test-data-2.0/basics/optionalAttr.py", line 3, in @record WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' Default value for attribute `y` of record `C` must have type `int`. But the default value has type `str`. -## File optionalAttr.py +## File test-data-2.0/basics/optionalAttr.py ## Parameter declared in line 6: - y: int = 'foo' + y: int = 'foo' \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr2.err b/python/test-data-2.0/basics/optionalAttr2.err index 4983d25e..b93e0b2b 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err +++ b/python/test-data-2.0/basics/optionalAttr2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr2.py", line 9, in + File "test-data-2.0/basics/optionalAttr2.py", line 9, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt mindestens 2 Argumente. Gegeben: 1 Argument -## Datei optionalAttr2.py +## Datei test-data-2.0/basics/optionalAttr2.py ## Aufruf in Zeile 9: -c = C(1) +c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr2.err_en b/python/test-data-2.0/basics/optionalAttr2.err_en index 706c936c..7c78815c 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err_en +++ b/python/test-data-2.0/basics/optionalAttr2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr2.py", line 9, in + File "test-data-2.0/basics/optionalAttr2.py", line 9, in c = C(1) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes at least 2 arguments. Given: 1 argument -## File optionalAttr2.py +## File test-data-2.0/basics/optionalAttr2.py ## Call in line 9: -c = C(1) +c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr3.err b/python/test-data-2.0/basics/optionalAttr3.err index c22ad0d0..c6f0bb6e 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err +++ b/python/test-data-2.0/basics/optionalAttr3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr3.py", line 9, in + File "test-data-2.0/basics/optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` akzeptiert höchstens 3 Argumente. Gegeben: 4 Argumente -## Datei optionalAttr3.py +## Datei test-data-2.0/basics/optionalAttr3.py ## Aufruf in Zeile 9: -c = C(1, 2, 3, 4) +c = C(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr3.err_en b/python/test-data-2.0/basics/optionalAttr3.err_en index 1c9d97bd..b4637346 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err_en +++ b/python/test-data-2.0/basics/optionalAttr3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "optionalAttr3.py", line 9, in + File "test-data-2.0/basics/optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes at most 3 arguments. Given: 4 arguments -## File optionalAttr3.py +## File test-data-2.0/basics/optionalAttr3.py ## Call in line 9: -c = C(1, 2, 3, 4) +c = C(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/partial.err b/python/test-data-2.0/basics/partial.err index c0a4c30e..6c1f7809 100644 --- a/python/test-data-2.0/basics/partial.err +++ b/python/test-data-2.0/basics/partial.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "partial.py", line 1, in + File "test-data-2.0/basics/partial.py", line 1, in def foo(i: int, j) -> None: WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typannotation. -## Datei partial.py +## Datei test-data-2.0/basics/partial.py ## Parameter deklariert in Zeile 1: -def foo(i: int, j) -> None: +def foo(i: int, j) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/basics/partial.err_en b/python/test-data-2.0/basics/partial.err_en index 4771107c..f14c885b 100644 --- a/python/test-data-2.0/basics/partial.err_en +++ b/python/test-data-2.0/basics/partial.err_en @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "partial.py", line 1, in + File "test-data-2.0/basics/partial.py", line 1, in def foo(i: int, j) -> None: WyppTypeError: Parameter `j` of function `foo` requires a type annotation. -## File partial.py +## File test-data-2.0/basics/partial.py ## Parameter declared in line 1: -def foo(i: int, j) -> None: +def foo(i: int, j) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/basics/record.err b/python/test-data-2.0/basics/record.err index 591d41ef..c69b34b5 100644 --- a/python/test-data-2.0/basics/record.err +++ b/python/test-data-2.0/basics/record.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "record.py", line 8, in + File "test-data-2.0/basics/record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" @@ -7,11 +7,11 @@ 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 record.py +## Datei test-data-2.0/basics/record.py ## Fehlerhafter Aufruf in Zeile 8: p = Person("Alice", "30") ## Typ deklariert in Zeile 6: - age: int + age: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/record.err_en b/python/test-data-2.0/basics/record.err_en index 102e3bf7..524ea4ad 100644 --- a/python/test-data-2.0/basics/record.err_en +++ b/python/test-data-2.0/basics/record.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "record.py", line 8, in + File "test-data-2.0/basics/record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" @@ -7,11 +7,11 @@ 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 record.py +## File test-data-2.0/basics/record.py ## Problematic call in line 8: p = Person("Alice", "30") ## Type declared in line 6: - age: int + age: int \ No newline at end of file diff --git a/python/test-data-2.0/basics/stack.err b/python/test-data-2.0/basics/stack.err index 69ecbdac..115c2f97 100644 --- a/python/test-data-2.0/basics/stack.err +++ b/python/test-data-2.0/basics/stack.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "stack.py", line 7, in + File "test-data-2.0/basics/stack.py", line 7, in factorial(5) - File "stack.py", line 5, in factorial + File "test-data-2.0/basics/stack.py", line 5, in factorial return factorial(n - 1) * n - File "stack.py", line 5, in factorial + File "test-data-2.0/basics/stack.py", line 5, in factorial return factorial(n - 1) * n - File "stack.py", line 5, in factorial + File "test-data-2.0/basics/stack.py", line 5, in factorial return factorial(n - 1) * n [Previous line repeated 2 more times] - File "stack.py", line 3, in factorial + File "test-data-2.0/basics/stack.py", line 3, in factorial raise ValueError('kein Bock') -ValueError: kein Bock +ValueError: kein Bock \ No newline at end of file diff --git a/python/test-data-2.0/basics/staticmethod.err b/python/test-data-2.0/basics/staticmethod.err index b50b4a04..9232e6ac 100644 --- a/python/test-data-2.0/basics/staticmethod.err +++ b/python/test-data-2.0/basics/staticmethod.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "staticmethod.py", line 6, in + File "test-data-2.0/basics/staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" @@ -7,11 +7,11 @@ 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 staticmethod.py +## Datei test-data-2.0/basics/staticmethod.py ## Fehlerhafter Aufruf in Zeile 6: C.method("2") ## Typ deklariert in Zeile 3: - def method(y: int) -> int: + def method(y: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/staticmethod.err_en b/python/test-data-2.0/basics/staticmethod.err_en index b6594829..5fb1b1d9 100644 --- a/python/test-data-2.0/basics/staticmethod.err_en +++ b/python/test-data-2.0/basics/staticmethod.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "staticmethod.py", line 6, in + File "test-data-2.0/basics/staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" @@ -7,11 +7,11 @@ 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 staticmethod.py +## File test-data-2.0/basics/staticmethod.py ## Problematic call in line 6: C.method("2") ## Type declared in line 3: - def method(y: int) -> int: + def method(y: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/testCallable.err b/python/test-data-2.0/basics/testCallable.err index 579ebbaf..7a803368 100644 --- a/python/test-data-2.0/basics/testCallable.err +++ b/python/test-data-2.0/basics/testCallable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testCallable.py", line 6, in + File "test-data-2.0/basics/testCallable.py", line 6, in foo(42) WyppTypeError: 42 @@ -7,11 +7,11 @@ 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 testCallable.py +## Datei test-data-2.0/basics/testCallable.py ## Fehlerhafter Aufruf in Zeile 6: foo(42) ## Typ deklariert in Zeile 3: -def foo(f: Callable[[int, bool], str]) -> int: +def foo(f: Callable[[int, bool], str]) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooFewArgs.err b/python/test-data-2.0/basics/tooFewArgs.err index 37ef66f8..2e1c78cd 100644 --- a/python/test-data-2.0/basics/tooFewArgs.err +++ b/python/test-data-2.0/basics/tooFewArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooFewArgs.py", line 4, in + File "test-data-2.0/basics/tooFewArgs.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei tooFewArgs.py +## Datei test-data-2.0/basics/tooFewArgs.py ## Aufruf in Zeile 4: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooFewAttrs.err b/python/test-data-2.0/basics/tooFewAttrs.err index 7012682e..45e49425 100644 --- a/python/test-data-2.0/basics/tooFewAttrs.err +++ b/python/test-data-2.0/basics/tooFewAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooFewAttrs.py", line 8, in + File "test-data-2.0/basics/tooFewAttrs.py", line 8, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei tooFewAttrs.py +## Datei test-data-2.0/basics/tooFewAttrs.py ## Aufruf in Zeile 8: -c = C(1) +c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyArgs.err b/python/test-data-2.0/basics/tooManyArgs.err index e8358624..7ae19a03 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err +++ b/python/test-data-2.0/basics/tooManyArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooManyArgs.py", line 4, in + File "test-data-2.0/basics/tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 3 Argumente -## Datei tooManyArgs.py +## Datei test-data-2.0/basics/tooManyArgs.py ## Aufruf in Zeile 4: -foo(1, 2, 3) +foo(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyArgs.err_en b/python/test-data-2.0/basics/tooManyArgs.err_en index 2fcd8b43..4c74459f 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err_en +++ b/python/test-data-2.0/basics/tooManyArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooManyArgs.py", line 4, in + File "test-data-2.0/basics/tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes 2 arguments. Given: 3 arguments -## File tooManyArgs.py +## File test-data-2.0/basics/tooManyArgs.py ## Call in line 4: -foo(1, 2, 3) +foo(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyAttrs.err b/python/test-data-2.0/basics/tooManyAttrs.err index 47ee237c..9e4f839e 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err +++ b/python/test-data-2.0/basics/tooManyAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooManyAttrs.py", line 8, in + File "test-data-2.0/basics/tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt 2 Argumente. Gegeben: 3 Argumente -## Datei tooManyAttrs.py +## Datei test-data-2.0/basics/tooManyAttrs.py ## Aufruf in Zeile 8: -c = C(1, 2, 3) +c = C(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyAttrs.err_en b/python/test-data-2.0/basics/tooManyAttrs.err_en index 7176e373..9f6f760d 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err_en +++ b/python/test-data-2.0/basics/tooManyAttrs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "tooManyAttrs.py", line 8, in + File "test-data-2.0/basics/tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes 2 arguments. Given: 3 arguments -## File tooManyAttrs.py +## File test-data-2.0/basics/tooManyAttrs.py ## Call in line 8: -c = C(1, 2, 3) +c = C(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/typeAlias.err b/python/test-data-2.0/basics/typeAlias.err index cc67722d..4cf634bb 100644 --- a/python/test-data-2.0/basics/typeAlias.err +++ b/python/test-data-2.0/basics/typeAlias.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "typeAlias.py", line 7, in + File "test-data-2.0/basics/typeAlias.py", line 7, in foo('foo') WyppTypeError: 'foo' @@ -7,11 +7,11 @@ WyppTypeError: 'foo' Der Aufruf der Funktion `foo` erwartet Wert vom Typ `T` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei typeAlias.py +## Datei test-data-2.0/basics/typeAlias.py ## Fehlerhafter Aufruf in Zeile 7: foo('foo') ## Typ deklariert in Zeile 3: -def foo(i: T) -> T: +def foo(i: T) -> T: \ No newline at end of file diff --git a/python/test-data-2.0/basics/typeAlias.err_en b/python/test-data-2.0/basics/typeAlias.err_en index 67a934d1..498192e5 100644 --- a/python/test-data-2.0/basics/typeAlias.err_en +++ b/python/test-data-2.0/basics/typeAlias.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "typeAlias.py", line 7, in + File "test-data-2.0/basics/typeAlias.py", line 7, in foo('foo') WyppTypeError: 'foo' @@ -7,11 +7,11 @@ WyppTypeError: 'foo' The call of function `foo` expects value of type `T` as 1st argument. But the value given has type `str`. -## File typeAlias.py +## File test-data-2.0/basics/typeAlias.py ## Problematic call in line 7: foo('foo') ## Type declared in line 3: -def foo(i: T) -> T: +def foo(i: T) -> T: \ No newline at end of file diff --git a/python/test-data-2.0/basics/typeAlias2.err b/python/test-data-2.0/basics/typeAlias2.err index 7221683a..cbe3330c 100644 --- a/python/test-data-2.0/basics/typeAlias2.err +++ b/python/test-data-2.0/basics/typeAlias2.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "typeAlias2.py", line 7, in + File "test-data-2.0/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 typeAlias2.py +## Datei test-data-2.0/basics/typeAlias2.py ## Fehlerhafter Aufruf in Zeile 7: foo(['foo']) ## Typ deklariert in Zeile 3: -def foo(x: list[T]): +def foo(x: list[T]): \ No newline at end of file diff --git a/python/test-data-2.0/basics/typeAlias2.err_en b/python/test-data-2.0/basics/typeAlias2.err_en index 2363f6c3..61d38cb3 100644 --- a/python/test-data-2.0/basics/typeAlias2.err_en +++ b/python/test-data-2.0/basics/typeAlias2.err_en @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "typeAlias2.py", line 7, in + File "test-data-2.0/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 typeAlias2.py +## File test-data-2.0/basics/typeAlias2.py ## Problematic call in line 7: foo(['foo']) ## Type declared in line 3: -def foo(x: list[T]): +def foo(x: list[T]): \ No newline at end of file diff --git a/python/test-data-2.0/extras/testTypes2.err b/python/test-data-2.0/extras/testTypes2.err index ea3b0a81..df170e1e 100644 --- a/python/test-data-2.0/extras/testTypes2.err +++ b/python/test-data-2.0/extras/testTypes2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypes2.py", line 4, in + File "test-data-2.0/extras/testTypes2.py", line 4, in inc("1") WyppTypeError: "1" @@ -7,11 +7,11 @@ WyppTypeError: "1" Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei testTypes2.py +## Datei test-data-2.0/extras/testTypes2.py ## Fehlerhafter Aufruf in Zeile 4: inc("1") ## Typ deklariert in Zeile 1: -def inc(x: int) -> int: +def inc(x: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/declared-at-missing.err-3.12 b/python/test-data-2.0/failing/declared-at-missing.err-3.12 index 5d7e02f9..b73543d8 100644 --- a/python/test-data-2.0/failing/declared-at-missing.err-3.12 +++ b/python/test-data-2.0/failing/declared-at-missing.err-3.12 @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "declared-at-missing.py", line 22, in + File "test-data-2.0/failing/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 declared-at-missing.py +## Datei test-data-2.0/failing/declared-at-missing.py ## Fehlerhafter Aufruf in Zeile 22: semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) ## Typ deklariert in Zeile 19: - courses: tuple[CourseM, ...] + courses: tuple[CourseM, ...] \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType.err b/python/test-data-2.0/failing/invalidType.err index bd1107ed..699464d7 100644 --- a/python/test-data-2.0/failing/invalidType.err +++ b/python/test-data-2.0/failing/invalidType.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "invalidType.py", line 9, in + File "test-data-2.0/failing/invalidType.py", line 9, in foo() WyppTypeError: ungültiger Typ `Union(list(int), list[float])` Wolltest du `Union[list(int), list[float]]` schreiben? -## Datei invalidType.py +## Datei test-data-2.0/failing/invalidType.py ## Typ deklariert in Zeile 6: -def foo() -> Union(list(int), list[float]): +def foo() -> Union(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType2.err b/python/test-data-2.0/failing/invalidType2.err index bff32582..cd55d413 100644 --- a/python/test-data-2.0/failing/invalidType2.err +++ b/python/test-data-2.0/failing/invalidType2.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "invalidType2.py", line 5, in + File "test-data-2.0/failing/invalidType2.py", line 5, in T = Union(list(int), list[float]) -TypeError: 'type' object is not iterable +TypeError: 'type' object is not iterable \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType3.err b/python/test-data-2.0/failing/invalidType3.err index 71e51646..dbe1d40c 100644 --- a/python/test-data-2.0/failing/invalidType3.err +++ b/python/test-data-2.0/failing/invalidType3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "invalidType3.py", line 9, in + File "test-data-2.0/failing/invalidType3.py", line 9, in foo() WyppTypeError: ungültiger Typ `Optional(list(int), list[float])` Wolltest du `Optional[list(int), list[float]]` schreiben? -## Datei invalidType3.py +## Datei test-data-2.0/failing/invalidType3.py ## Typ deklariert in Zeile 6: -def foo() -> Optional(list(int), list[float]): +def foo() -> Optional(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType4.err b/python/test-data-2.0/failing/invalidType4.err index 47b38bcf..31f82ddf 100644 --- a/python/test-data-2.0/failing/invalidType4.err +++ b/python/test-data-2.0/failing/invalidType4.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "invalidType4.py", line 9, in + File "test-data-2.0/failing/invalidType4.py", line 9, in foo() WyppTypeError: ungültiger Typ `Optional[list[int], list[float]]` -## Datei invalidType4.py +## Datei test-data-2.0/failing/invalidType4.py ## Typ deklariert in Zeile 6: -def foo() -> Optional[list[int], list[float]]: +def foo() -> Optional[list[int], list[float]]: \ No newline at end of file diff --git a/python/test-data-2.0/failing/main.err b/python/test-data-2.0/failing/main.err index a3405fac..a0a978fd 100644 --- a/python/test-data-2.0/failing/main.err +++ b/python/test-data-2.0/failing/main.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "main.py", line 4, in + File "test-data-2.0/failing/main.py", line 4, in print(mod.foo(1)) - File "mod.py", line 5, in foo + File "test-data-2.0/modules/B/mod.py", line 5, in foo return bar(i) WyppTypeError: 1 @@ -9,11 +9,11 @@ WyppTypeError: 1 Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei mod.py +## Datei test-data-2.0/modules/B/mod.py ## Fehlerhafter Aufruf in Zeile 5: return bar(i) ## Typ deklariert in Zeile 1: -def bar(s: str) -> int: +def bar(s: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testABCMeta.err b/python/test-data-2.0/failing/testABCMeta.err index b58a7a1f..41e956ea 100644 --- a/python/test-data-2.0/failing/testABCMeta.err +++ b/python/test-data-2.0/failing/testABCMeta.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "testABCMeta.py", line 28, in + File "test-data-2.0/failing/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-2.0/failing/testForwardRef2.err b/python/test-data-2.0/failing/testForwardRef2.err index b155adf6..ac96f0c0 100644 --- a/python/test-data-2.0/failing/testForwardRef2.err +++ b/python/test-data-2.0/failing/testForwardRef2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "testForwardRef2.py", line 10, in + File "test-data-2.0/failing/testForwardRef2.py", line 10, in t = Test(FooX()) WyppTypeError: ungültiger Typ `Foo` -## Datei testForwardRef2.py +## Datei test-data-2.0/failing/testForwardRef2.py ## Typ deklariert in Zeile 2: - def __init__(self, foo: 'Foo'): + def __init__(self, foo: 'Foo'): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testForwardRef4.err-3.12 b/python/test-data-2.0/failing/testForwardRef4.err-3.12 index 610aa3f5..6805c80f 100644 --- a/python/test-data-2.0/failing/testForwardRef4.err-3.12 +++ b/python/test-data-2.0/failing/testForwardRef4.err-3.12 @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "testForwardRef4.py", line 11, in + File "test-data-2.0/failing/testForwardRef4.py", line 11, in t = Test(Foo()) WyppTypeError: ungültiger Typ `FooX` -## Datei testForwardRef4.py +## Datei test-data-2.0/failing/testForwardRef4.py ## Typ deklariert in Zeile 5: - foo: 'FooX' + foo: 'FooX' \ No newline at end of file diff --git a/python/test-data-2.0/failing/testForwardRef5.err-3.12 b/python/test-data-2.0/failing/testForwardRef5.err-3.12 index 7a625d40..931c9995 100644 --- a/python/test-data-2.0/failing/testForwardRef5.err-3.12 +++ b/python/test-data-2.0/failing/testForwardRef5.err-3.12 @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "testForwardRef5.py", line 22, in + File "test-data-2.0/failing/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 testForwardRef5.py +## Datei test-data-2.0/failing/testForwardRef5.py ## Fehlerhafter Aufruf in Zeile 22: garage = Garage(cars=[Car(color='red'), "Not A Car"]) ## Typ deklariert in Zeile 11: - cars: list['Car'] + cars: list['Car'] \ No newline at end of file diff --git a/python/test-data-2.0/failing/testGetSource.err b/python/test-data-2.0/failing/testGetSource.err index ee613675..9ff1685b 100644 --- a/python/test-data-2.0/failing/testGetSource.err +++ b/python/test-data-2.0/failing/testGetSource.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "testGetSource.py", line 11, in + File "test-data-2.0/failing/testGetSource.py", line 11, in Art = Literal('klein','mittag') # <= problem is here - File "writeYourProgram.py", line 92, in _invalidCall + File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) - File "errors.py", line 79, in invalidType + File "src/errors.py", line 79, in invalidType raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` Wolltest du `Literal['klein', 'mittag']` schreiben? -## Datei testGetSource.py +## Datei test-data-2.0/failing/testGetSource.py ## Typ deklariert in Zeile 11: -Art = Literal('klein','mittag') # <= problem is here +Art = Literal('klein','mittag') # <= problem is here \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses1.err b/python/test-data-2.0/failing/testHintParentheses1.err index c2c44394..1cd0aec0 100644 --- a/python/test-data-2.0/failing/testHintParentheses1.err +++ b/python/test-data-2.0/failing/testHintParentheses1.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testHintParentheses1.py", line 8, in + File "test-data-2.0/failing/testHintParentheses1.py", line 8, in check(foo([1,2,3]), 3) WyppTypeError: ungültiger Typ `list(int)` Wolltest du `list[int]` schreiben? -## Datei testHintParentheses1.py +## Datei test-data-2.0/failing/testHintParentheses1.py ## Typ deklariert in Zeile 5: -def foo(l: list(int)) -> int: +def foo(l: list(int)) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses2.err b/python/test-data-2.0/failing/testHintParentheses2.err index 7140ff7d..f28b4d98 100644 --- a/python/test-data-2.0/failing/testHintParentheses2.err +++ b/python/test-data-2.0/failing/testHintParentheses2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "testHintParentheses2.py", line 8, in + File "test-data-2.0/failing/testHintParentheses2.py", line 8, in foo(1, {}) WyppTypeError: ungültiger Typ `dict[1, list(int)]` -## Datei testHintParentheses2.py +## Datei test-data-2.0/failing/testHintParentheses2.py ## Typ deklariert in Zeile 5: -def foo(a: 'int', b: 'dict[1, list(int)]') -> int: +def foo(a: 'int', b: 'dict[1, list(int)]') -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses3.err b/python/test-data-2.0/failing/testHintParentheses3.err index 6673e629..89f835a6 100644 --- a/python/test-data-2.0/failing/testHintParentheses3.err +++ b/python/test-data-2.0/failing/testHintParentheses3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testHintParentheses3.py", line 9, in + File "test-data-2.0/failing/testHintParentheses3.py", line 9, in foo() WyppTypeError: ungültiger Typ `Union(list, str)` Wolltest du `Union[list, str]` schreiben? -## Datei testHintParentheses3.py +## Datei test-data-2.0/failing/testHintParentheses3.py ## Typ deklariert in Zeile 6: -def foo() -> Union(list, str): +def foo() -> Union(list, str): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testImpossible.err b/python/test-data-2.0/failing/testImpossible.err index 4ef98fd1..f53d7602 100644 --- a/python/test-data-2.0/failing/testImpossible.err +++ b/python/test-data-2.0/failing/testImpossible.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testImpossible.py", line 3, in + File "test-data-2.0/failing/testImpossible.py", line 3, in impossible() - File "writeYourProgram.py", line 331, in impossible + File "site-lib/wypp/writeYourProgram.py", line 331, in impossible raise errors.ImpossibleError(msg) -Das Unmögliche ist passiert! +Das Unmögliche ist passiert! \ No newline at end of file diff --git a/python/test-data-2.0/failing/testIndexError.err b/python/test-data-2.0/failing/testIndexError.err index 548499a2..61bd1310 100644 --- a/python/test-data-2.0/failing/testIndexError.err +++ b/python/test-data-2.0/failing/testIndexError.err @@ -1,6 +1,6 @@ Traceback (most recent call last): - File "testIndexError.py", line 6, in + File "test-data-2.0/failing/testIndexError.py", line 6, in foo([1,2,3]) - File "testIndexError.py", line 3, in foo + File "test-data-2.0/failing/testIndexError.py", line 3, in foo x = l[42] -IndexError: list index out of range +IndexError: list index out of range \ No newline at end of file diff --git a/python/test-data-2.0/failing/testInvalidLiteral.err b/python/test-data-2.0/failing/testInvalidLiteral.err index de63bb47..f3b69339 100644 --- a/python/test-data-2.0/failing/testInvalidLiteral.err +++ b/python/test-data-2.0/failing/testInvalidLiteral.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "testInvalidLiteral.py", line 8, in + File "test-data-2.0/failing/testInvalidLiteral.py", line 8, in gameFull([['x']]) WyppTypeError: ungültiger Typ `list[list[['x', 'o', '-']]]` -## Datei testInvalidLiteral.py +## Datei test-data-2.0/failing/testInvalidLiteral.py ## Typ deklariert in Zeile 5: -def gameFull(game:Game)->bool: +def gameFull(game:Game)->bool: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testIterableImplicitAny.err b/python/test-data-2.0/failing/testIterableImplicitAny.err index 5b7fd00c..34281df4 100644 --- a/python/test-data-2.0/failing/testIterableImplicitAny.err +++ b/python/test-data-2.0/failing/testIterableImplicitAny.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testIterableImplicitAny.py", line 13, in + File "test-data-2.0/failing/testIterableImplicitAny.py", line 13, in foo(NotIterable()) WyppTypeError: NotIterable @@ -7,11 +7,11 @@ WyppTypeError: NotIterable Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Iterable` als erstes Argument. Aber der übergebene Wert hat Typ `NotIterable`. -## Datei testIterableImplicitAny.py +## Datei test-data-2.0/failing/testIterableImplicitAny.py ## Fehlerhafter Aufruf in Zeile 13: foo(NotIterable()) ## Typ deklariert in Zeile 7: -def foo(it: Iterable) -> int: +def foo(it: Iterable) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testLiteral1.err b/python/test-data-2.0/failing/testLiteral1.err index ff66d37b..093a11d4 100644 --- a/python/test-data-2.0/failing/testLiteral1.err +++ b/python/test-data-2.0/failing/testLiteral1.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "testLiteral1.py", line 3, in + File "test-data-2.0/failing/testLiteral1.py", line 3, in T = Literal('a', 'b') - File "writeYourProgram.py", line 92, in _invalidCall + File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) - File "errors.py", line 79, in invalidType + File "src/errors.py", line 79, in invalidType raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('a', 'b')` Wolltest du `Literal['a', 'b']` schreiben? -## Datei testLiteral1.py +## Datei test-data-2.0/failing/testLiteral1.py ## Typ deklariert in Zeile 3: -T = Literal('a', 'b') +T = Literal('a', 'b') \ No newline at end of file diff --git a/python/test-data-2.0/failing/testLockFactory.err b/python/test-data-2.0/failing/testLockFactory.err index 82aad7e1..ec9f8552 100644 --- a/python/test-data-2.0/failing/testLockFactory.err +++ b/python/test-data-2.0/failing/testLockFactory.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testLockFactory.py", line 15, in + File "test-data-2.0/failing/testLockFactory.py", line 15, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,11 +7,11 @@ 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 testLockFactory.py +## Datei test-data-2.0/failing/testLockFactory.py ## Fehlerhafter Aufruf in Zeile 15: foo("not a lock") ## Typ deklariert in Zeile 6: -def foo(lock: wypp.LockFactory) -> None: +def foo(lock: wypp.LockFactory) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testLockFactory2.err b/python/test-data-2.0/failing/testLockFactory2.err index 9e8c8c7b..64b4fb94 100644 --- a/python/test-data-2.0/failing/testLockFactory2.err +++ b/python/test-data-2.0/failing/testLockFactory2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testLockFactory2.py", line 14, in + File "test-data-2.0/failing/testLockFactory2.py", line 14, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,11 +7,11 @@ 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 testLockFactory2.py +## Datei test-data-2.0/failing/testLockFactory2.py ## Fehlerhafter Aufruf in Zeile 14: foo("not a lock") ## Typ deklariert in Zeile 6: -def foo(l: wypp.Lock) -> None: +def foo(l: wypp.Lock) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testMissingReturn.err b/python/test-data-2.0/failing/testMissingReturn.err index 39032b7e..7fe943d7 100644 --- a/python/test-data-2.0/failing/testMissingReturn.err +++ b/python/test-data-2.0/failing/testMissingReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testMissingReturn.py", line 6, in + File "test-data-2.0/failing/testMissingReturn.py", line 6, in print(billigStrom(500)) - File "testMissingReturn.py", line 4, in billigStrom + File "test-data-2.0/failing/testMissingReturn.py", line 4, in billigStrom pass WyppTypeError: kein Rückgabewert vorhanden @@ -9,11 +9,11 @@ WyppTypeError: kein Rückgabewert vorhanden Rückgabewert vom Typ `float` erwartet bei Aufruf der Funktion `billigStrom`. Aber kein Rückgabewert vorhanden. -## Datei testMissingReturn.py +## Datei test-data-2.0/failing/testMissingReturn.py ## Rückgabetyp deklariert in Zeile 3: def billigStrom(kwh: float) -> float: ## Aufruf in Zeile 6 führt dazu, dass die Funktion keinen Wert zurückgibt: -print(billigStrom(500)) +print(billigStrom(500)) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 index cb2cf0bd..c6b5717d 100644 --- a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 +++ b/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testRecordSetTypeForwardRef.py", line 15, in + File "test-data-2.0/failing/testRecordSetTypeForwardRef.py", line 15, in m() - File "testRecordSetTypeForwardRef.py", line 13, in m + File "test-data-2.0/failing/testRecordSetTypeForwardRef.py", line 13, in m r.x = "hello" WyppTypeError: "hello" @@ -9,11 +9,11 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `A`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei testRecordSetTypeForwardRef.py +## Datei test-data-2.0/failing/testRecordSetTypeForwardRef.py ## Fehlerhafte Zuweisung in Zeile 13: r.x = "hello" ## Typ deklariert in Zeile 6: - x: A + x: A \ No newline at end of file diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 b/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 index ac1f70d9..71bf8cee 100644 --- a/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 +++ b/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testRecordSetTypes.py", line 12, in + File "test-data-2.0/failing/testRecordSetTypes.py", line 12, in m() - File "testRecordSetTypes.py", line 10, in m + File "test-data-2.0/failing/testRecordSetTypes.py", line 10, in m r.x = "hello" WyppTypeError: "hello" @@ -9,11 +9,11 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei testRecordSetTypes.py +## Datei test-data-2.0/failing/testRecordSetTypes.py ## Fehlerhafte Zuweisung in Zeile 10: r.x = "hello" ## Typ deklariert in Zeile 5: - x : int + x : int \ No newline at end of file diff --git a/python/test-data-2.0/failing/testRecordTypes.err-3.12 b/python/test-data-2.0/failing/testRecordTypes.err-3.12 index 7901b40a..d296bf88 100644 --- a/python/test-data-2.0/failing/testRecordTypes.err-3.12 +++ b/python/test-data-2.0/failing/testRecordTypes.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testRecordTypes.py", line 8, in + File "test-data-2.0/failing/testRecordTypes.py", line 8, in p = Point(1, '5') WyppTypeError: '5' @@ -7,11 +7,11 @@ 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 testRecordTypes.py +## Datei test-data-2.0/failing/testRecordTypes.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(1, '5') ## Typ deklariert in Zeile 6: - y: int + y: int \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTodo.err b/python/test-data-2.0/failing/testTodo.err index 51ff27af..df03d1db 100644 --- a/python/test-data-2.0/failing/testTodo.err +++ b/python/test-data-2.0/failing/testTodo.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTodo.py", line 3, in + File "test-data-2.0/failing/testTodo.py", line 3, in todo() - File "writeYourProgram.py", line 326, in todo + File "site-lib/wypp/writeYourProgram.py", line 326, in todo raise errors.TodoError(msg) -TODO +TODO \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTraceback.err b/python/test-data-2.0/failing/testTraceback.err index 0e6d16ea..57bc5bc9 100644 --- a/python/test-data-2.0/failing/testTraceback.err +++ b/python/test-data-2.0/failing/testTraceback.err @@ -1,6 +1,6 @@ Traceback (most recent call last): - File "testTraceback.py", line 9, in + File "test-data-2.0/failing/testTraceback.py", line 9, in foo(lst) - File "testTraceback.py", line 7, in foo + File "test-data-2.0/failing/testTraceback.py", line 7, in foo print(lst[10]) -IndexError: list index out of range +IndexError: list index out of range \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTraceback2.err b/python/test-data-2.0/failing/testTraceback2.err index d87cc74a..cb7b5f82 100644 --- a/python/test-data-2.0/failing/testTraceback2.err +++ b/python/test-data-2.0/failing/testTraceback2.err @@ -1,4 +1,4 @@ - File "testTraceback2.py", line 3 +File "/Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testTraceback2.py", line 3 lst = [1,2,3 ^ -SyntaxError: '[' was never closed +SyntaxError: '[' was never closed \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTraceback3.err b/python/test-data-2.0/failing/testTraceback3.err index f262f399..bcf0ef4c 100644 --- a/python/test-data-2.0/failing/testTraceback3.err +++ b/python/test-data-2.0/failing/testTraceback3.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "testTraceback3.py", line 2, in + File "test-data-2.0/failing/testTraceback3.py", line 2, in print([1,2,3][10]) -IndexError: list index out of range +IndexError: list index out of range \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypes1.err b/python/test-data-2.0/failing/testTypes1.err index e11aeef6..2baa29b8 100644 --- a/python/test-data-2.0/failing/testTypes1.err +++ b/python/test-data-2.0/failing/testTypes1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypes1.py", line 4, in + File "test-data-2.0/failing/testTypes1.py", line 4, in inc("1") WyppTypeError: "1" @@ -7,11 +7,11 @@ WyppTypeError: "1" Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei testTypes1.py +## Datei test-data-2.0/failing/testTypes1.py ## Fehlerhafter Aufruf in Zeile 4: inc("1") ## Typ deklariert in Zeile 1: -def inc(x: int) -> int: +def inc(x: int) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesDict3.err b/python/test-data-2.0/failing/testTypesDict3.err index 155259ab..91e44513 100644 --- a/python/test-data-2.0/failing/testTypesDict3.err +++ b/python/test-data-2.0/failing/testTypesDict3.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "testTypesDict3.py", line 11, in + File "test-data-2.0/failing/testTypesDict3.py", line 11, in foo({'y': func}) - File "testTypesDict3.py", line 8, in foo + File "test-data-2.0/failing/testTypesDict3.py", line 8, in foo return res WyppTypeError: ['xxx', 42] Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. -## Datei testTypesDict3.py +## Datei test-data-2.0/failing/testTypesDict3.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: @@ -19,4 +19,4 @@ def foo(d: dict[str, Callable[[], str]]) -> list[str]: ## Aufruf in Zeile 11 verursacht das fehlerhafte return: -foo({'y': func}) +foo({'y': func}) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesDict4.err b/python/test-data-2.0/failing/testTypesDict4.err index 77e510b5..a5b3aa36 100644 --- a/python/test-data-2.0/failing/testTypesDict4.err +++ b/python/test-data-2.0/failing/testTypesDict4.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "testTypesDict4.py", line 14, in + File "test-data-2.0/failing/testTypesDict4.py", line 14, in bar({'y': func}) # error - File "testTypesDict4.py", line 11, in bar + File "test-data-2.0/failing/testTypesDict4.py", line 11, in bar return foo(d) - File "testTypesDict4.py", line 8, in foo + File "test-data-2.0/failing/testTypesDict4.py", line 8, in foo return res WyppTypeError: [42, 'x'] Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`. -## Datei testTypesDict4.py +## Datei test-data-2.0/failing/testTypesDict4.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: @@ -21,4 +21,4 @@ def foo(d: dict[str, Callable[[], str]]) -> list[str]: ## Aufruf in Zeile 11 verursacht das fehlerhafte return: - return foo(d) + return foo(d) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns.err b/python/test-data-2.0/failing/testTypesHigherOrderFuns.err index a0feee64..8e6321ed 100644 --- a/python/test-data-2.0/failing/testTypesHigherOrderFuns.err +++ b/python/test-data-2.0/failing/testTypesHigherOrderFuns.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "testTypesHigherOrderFuns.py", line 10, in + File "test-data-2.0/failing/testTypesHigherOrderFuns.py", line 10, in map(["hello", "1"], lambda x: x) - File "testTypesHigherOrderFuns.py", line 7, in map + File "test-data-2.0/failing/testTypesHigherOrderFuns.py", line 7, in map return res WyppTypeError: ['hello', '1'] Rückgabewert vom Typ `list[int]` erwartet bei Aufruf der Funktion `map`. -## Datei testTypesHigherOrderFuns.py +## Datei test-data-2.0/failing/testTypesHigherOrderFuns.py ## Rückgabetyp deklariert in Zeile 3: def map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]: @@ -19,4 +19,4 @@ def map(container: Iterable[str], fun: Callable[[str], int]) -> list[ ## Aufruf in Zeile 10 verursacht das fehlerhafte return: -map(["hello", "1"], lambda x: x) +map(["hello", "1"], lambda x: x) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err index c5eee336..f314e0eb 100644 --- a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err +++ b/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypesHigherOrderFuns3.py", line 38, in + File "test-data-2.0/failing/testTypesHigherOrderFuns3.py", line 38, in homePoints: Callable[[GameResult], int] = mkGamePoints(42) WyppTypeError: 42 @@ -7,11 +7,11 @@ 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 testTypesHigherOrderFuns3.py +## Datei test-data-2.0/failing/testTypesHigherOrderFuns3.py ## Fehlerhafter Aufruf in Zeile 38: homePoints: Callable[[GameResult], int] = mkGamePoints(42) ## Typ deklariert in Zeile 35: -def mkGamePoints(cmp: Callable[[int, int], bool]) -> Callable[[GameResult], int]: +def mkGamePoints(cmp: Callable[[int, int], bool]) -> Callable[[GameResult], int]: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos1.err b/python/test-data-2.0/failing/testTypesProtos1.err index 20bd1e6b..55d08a22 100644 --- a/python/test-data-2.0/failing/testTypesProtos1.err +++ b/python/test-data-2.0/failing/testTypesProtos1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesProtos1.py", line 21, in + File "test-data-2.0/failing/testTypesProtos1.py", line 21, in doSomething(Dog()) - File "testTypesProtos1.py", line 19, in doSomething + File "test-data-2.0/failing/testTypesProtos1.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,11 +9,11 @@ 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 testTypesProtos1.py +## Datei test-data-2.0/failing/testTypesProtos1.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) ## Typ deklariert in Zeile 13: - def makeSound(self, loadness: int) -> str: + def makeSound(self, loadness: int) -> str: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos2.err b/python/test-data-2.0/failing/testTypesProtos2.err index 7ad021e5..c8a498a8 100644 --- a/python/test-data-2.0/failing/testTypesProtos2.err +++ b/python/test-data-2.0/failing/testTypesProtos2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesProtos2.py", line 21, in + File "test-data-2.0/failing/testTypesProtos2.py", line 21, in doSomething(Dog()) - File "testTypesProtos2.py", line 19, in doSomething + File "test-data-2.0/failing/testTypesProtos2.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,11 +9,11 @@ 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 testTypesProtos2.py +## Datei test-data-2.0/failing/testTypesProtos2.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) ## Typ deklariert in Zeile 13: - def makeSound(self, loadness: int) -> str: + def makeSound(self, loadness: int) -> str: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos3.err b/python/test-data-2.0/failing/testTypesProtos3.err index a9f0bea3..a746c055 100644 --- a/python/test-data-2.0/failing/testTypesProtos3.err +++ b/python/test-data-2.0/failing/testTypesProtos3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesProtos3.py", line 21, in + File "test-data-2.0/failing/testTypesProtos3.py", line 21, in doSomething(Dog()) - File "testTypesProtos3.py", line 19, in doSomething + File "test-data-2.0/failing/testTypesProtos3.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: @@ -9,11 +9,11 @@ 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 testTypesProtos3.py +## Datei test-data-2.0/failing/testTypesProtos3.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) ## Typ deklariert in Zeile 13: - def makeSound(loadness: int) -> str: # self parameter omitted! + def makeSound(loadness: int) -> str: # self parameter omitted! \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos4.err b/python/test-data-2.0/failing/testTypesProtos4.err index cb06b422..ad0b8b1e 100644 --- a/python/test-data-2.0/failing/testTypesProtos4.err +++ b/python/test-data-2.0/failing/testTypesProtos4.err @@ -1,9 +1,9 @@ Traceback (most recent call last): - File "testTypesProtos4.py", line 27, in + File "test-data-2.0/failing/testTypesProtos4.py", line 27, in print(foo(ConcreteWrong())) - File "testTypesProtos4.py", line 24, in foo + File "test-data-2.0/failing/testTypesProtos4.py", line 24, in foo return fn(2) - File "testTypesProtos4.py", line 20, in + File "test-data-2.0/failing/testTypesProtos4.py", line 20, in return lambda x: bar(x) # invalid call of bar with argument of type int WyppTypeError: 2 @@ -11,11 +11,11 @@ WyppTypeError: 2 Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei testTypesProtos4.py +## Datei test-data-2.0/failing/testTypesProtos4.py ## Fehlerhafter Aufruf in Zeile 20: return lambda x: bar(x) # invalid call of bar with argument of type int ## Typ deklariert in Zeile 15: -def bar(s: str) -> int: +def bar(s: str) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos6.err b/python/test-data-2.0/failing/testTypesProtos6.err index 2dcd966c..ed839f30 100644 --- a/python/test-data-2.0/failing/testTypesProtos6.err +++ b/python/test-data-2.0/failing/testTypesProtos6.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "testTypesProtos6.py", line 57, in + File "test-data-2.0/failing/testTypesProtos6.py", line 57, in print(computeTotalSize(root)) - File "testTypesProtos6.py", line 50, in computeTotalSize + File "test-data-2.0/failing/testTypesProtos6.py", line 50, in computeTotalSize fs.accept(visitor) - File "testTypesProtos6.py", line 19, in accept + File "test-data-2.0/failing/testTypesProtos6.py", line 19, in accept visitor.visitDirectory(self) - File "testTypesProtos6.py", line 41, in visitDirectory + File "test-data-2.0/failing/testTypesProtos6.py", line 41, in visitDirectory c.accept(self) - File "testTypesProtos6.py", line 28, in accept + File "test-data-2.0/failing/testTypesProtos6.py", line 28, in accept visitor.visitFile(self) WyppTypeError: <__wypp__.File object at 0x00> @@ -15,11 +15,11 @@ 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 testTypesProtos6.py +## Datei test-data-2.0/failing/testTypesProtos6.py ## Fehlerhafter Aufruf in Zeile 28: visitor.visitFile(self) ## Typ deklariert in Zeile 42: - def visitFile(self, file: str): + def visitFile(self, file: str): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos7.err b/python/test-data-2.0/failing/testTypesProtos7.err index 9056e7df..927c7fb1 100644 --- a/python/test-data-2.0/failing/testTypesProtos7.err +++ b/python/test-data-2.0/failing/testTypesProtos7.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "testTypesProtos7.py", line 76, in + File "test-data-2.0/failing/testTypesProtos7.py", line 76, in print(computeTotalSize(root)) - File "testTypesProtos7.py", line 69, in computeTotalSize + File "test-data-2.0/failing/testTypesProtos7.py", line 69, in computeTotalSize fs.accept(visitor) - File "testTypesProtos7.py", line 36, in accept + File "test-data-2.0/failing/testTypesProtos7.py", line 36, in accept visitor.visitDirectory(self) - File "testTypesProtos7.py", line 60, in visitDirectory + File "test-data-2.0/failing/testTypesProtos7.py", line 60, in visitDirectory c.accept(self) - File "testTypesProtos7.py", line 47, in accept + File "test-data-2.0/failing/testTypesProtos7.py", line 47, in accept visitor.visitFile(self) WyppTypeError: File('notes.txt') @@ -15,11 +15,11 @@ 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 testTypesProtos7.py +## Datei test-data-2.0/failing/testTypesProtos7.py ## Fehlerhafter Aufruf in Zeile 47: visitor.visitFile(self) ## Typ deklariert in Zeile 61: - def visitFile(self, f: str): + def visitFile(self, f: str): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos8.err b/python/test-data-2.0/failing/testTypesProtos8.err index 33e24c24..e5656903 100644 --- a/python/test-data-2.0/failing/testTypesProtos8.err +++ b/python/test-data-2.0/failing/testTypesProtos8.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesProtos8.py", line 12, in + File "test-data-2.0/failing/testTypesProtos8.py", line 12, in bar(Sub()) - File "testTypesProtos8.py", line 10, in bar + File "test-data-2.0/failing/testTypesProtos8.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,11 +9,11 @@ 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 testTypesProtos8.py +## Datei test-data-2.0/failing/testTypesProtos8.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") ## Typ deklariert in Zeile 6: - def foo(self, y: int, x: float): + def foo(self, y: int, x: float): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesProtos9.err b/python/test-data-2.0/failing/testTypesProtos9.err index 6d73ee5e..712591a4 100644 --- a/python/test-data-2.0/failing/testTypesProtos9.err +++ b/python/test-data-2.0/failing/testTypesProtos9.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesProtos9.py", line 12, in + File "test-data-2.0/failing/testTypesProtos9.py", line 12, in bar(Sub()) - File "testTypesProtos9.py", line 10, in bar + File "test-data-2.0/failing/testTypesProtos9.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,11 +9,11 @@ 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 testTypesProtos9.py +## Datei test-data-2.0/failing/testTypesProtos9.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") ## Typ deklariert in Zeile 6: - def foo(self, subX: int, subY: float): + def foo(self, subX: int, subY: float): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 index 4f3ce320..41306715 100644 --- a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 +++ b/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypesRecordInheritance.py", line 18, in + File "test-data-2.0/failing/testTypesRecordInheritance.py", line 18, in Point3D(1,2, "foo") WyppTypeError: "foo" @@ -7,11 +7,11 @@ 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 testTypesRecordInheritance.py +## Datei test-data-2.0/failing/testTypesRecordInheritance.py ## Fehlerhafter Aufruf in Zeile 18: Point3D(1,2, "foo") ## Typ deklariert in Zeile 11: - z: int + z: int \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesReturn.err b/python/test-data-2.0/failing/testTypesReturn.err index 7a9b048f..6dfe3a66 100644 --- a/python/test-data-2.0/failing/testTypesReturn.err +++ b/python/test-data-2.0/failing/testTypesReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesReturn.py", line 8, in + File "test-data-2.0/failing/testTypesReturn.py", line 8, in foo(False) - File "testTypesReturn.py", line 6, in foo + File "test-data-2.0/failing/testTypesReturn.py", line 6, in foo return 'you stupid' WyppTypeError: 'you stupid' @@ -9,7 +9,7 @@ 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 testTypesReturn.py +## Datei test-data-2.0/failing/testTypesReturn.py ## Rückgabetyp deklariert in Zeile 1: def foo(flag: bool) -> int: @@ -20,4 +20,4 @@ def foo(flag: bool) -> int: ## Aufruf in Zeile 8 verursacht das fehlerhafte return: -foo(False) +foo(False) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesSequence1.err b/python/test-data-2.0/failing/testTypesSequence1.err index e181f3fc..ea547219 100644 --- a/python/test-data-2.0/failing/testTypesSequence1.err +++ b/python/test-data-2.0/failing/testTypesSequence1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypesSequence1.py", line 10, in + File "test-data-2.0/failing/testTypesSequence1.py", line 10, in foo(1) # should fail WyppTypeError: 1 @@ -7,11 +7,11 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei testTypesSequence1.py +## Datei test-data-2.0/failing/testTypesSequence1.py ## Fehlerhafter Aufruf in Zeile 10: foo(1) # should fail ## Typ deklariert in Zeile 3: -def foo(seq: Sequence) -> None: +def foo(seq: Sequence) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesSequence2.err b/python/test-data-2.0/failing/testTypesSequence2.err index 3f7da9b7..62d8517b 100644 --- a/python/test-data-2.0/failing/testTypesSequence2.err +++ b/python/test-data-2.0/failing/testTypesSequence2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypesSequence2.py", line 11, in + File "test-data-2.0/failing/testTypesSequence2.py", line 11, in foo("Hello!") # should fail WyppTypeError: "Hello!" @@ -7,11 +7,11 @@ WyppTypeError: "Hello!" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence[int]` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei testTypesSequence2.py +## Datei test-data-2.0/failing/testTypesSequence2.py ## Fehlerhafter Aufruf in Zeile 11: foo("Hello!") # should fail ## Typ deklariert in Zeile 3: -def foo(seq: Sequence[int]) -> None: +def foo(seq: Sequence[int]) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.err b/python/test-data-2.0/failing/testTypesSubclassing1.err index abc22c8b..253b0520 100644 --- a/python/test-data-2.0/failing/testTypesSubclassing1.err +++ b/python/test-data-2.0/failing/testTypesSubclassing1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "testTypesSubclassing1.py", line 29, in + File "test-data-2.0/failing/testTypesSubclassing1.py", line 29, in feedAnimal(dog) - File "testTypesSubclassing1.py", line 26, in feedAnimal + File "test-data-2.0/failing/testTypesSubclassing1.py", line 26, in feedAnimal a.feed(AnimalFood('some cat food')) WyppTypeError: @@ -9,11 +9,11 @@ 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 testTypesSubclassing1.py +## Datei test-data-2.0/failing/testTypesSubclassing1.py ## Fehlerhafter Aufruf in Zeile 26: a.feed(AnimalFood('some cat food')) ## Typ deklariert in Zeile 22: - def feed(self, food: DogFood) -> None: + def feed(self, food: DogFood) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTypesTuple1.err b/python/test-data-2.0/failing/testTypesTuple1.err index b659b217..9e3c2041 100644 --- a/python/test-data-2.0/failing/testTypesTuple1.err +++ b/python/test-data-2.0/failing/testTypesTuple1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testTypesTuple1.py", line 5, in + File "test-data-2.0/failing/testTypesTuple1.py", line 5, in foo(1) WyppTypeError: 1 @@ -7,11 +7,11 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `tuple[int, ...]` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei testTypesTuple1.py +## Datei test-data-2.0/failing/testTypesTuple1.py ## Fehlerhafter Aufruf in Zeile 5: foo(1) ## Typ deklariert in Zeile 1: -def foo(l: tuple[int, ...]) -> int: +def foo(l: tuple[int, ...]) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.err b/python/test-data-2.0/failing/testWrongKeywordArg.err index 6af4ac79..ddf610a6 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testWrongKeywordArg.py", line 4, in + File "test-data-2.0/failing/testWrongKeywordArg.py", line 4, in foo(x=4) WyppTypeError: unbekanntes Schlüsselwort-Argument Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`. -## Datei testWrongKeywordArg.py +## Datei test-data-2.0/failing/testWrongKeywordArg.py ## Fehlerhafter Aufruf in Zeile 4: -foo(x=4) +foo(x=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 index 70dc7168..58014e11 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 +++ b/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testWrongKeywordArg2.py", line 8, in + File "test-data-2.0/failing/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 testWrongKeywordArg2.py +## Datei test-data-2.0/failing/testWrongKeywordArg2.py ## Fehlerhafter Aufruf in Zeile 8: -p = Point(foo=3, y=4) +p = Point(foo=3, y=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.err b/python/test-data-2.0/failing/testWrongKeywordArg3.err index 8197c40c..e3a60b53 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg3.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testWrongKeywordArg3.py", line 8, in + File "test-data-2.0/failing/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 testWrongKeywordArg3.py +## Datei test-data-2.0/failing/testWrongKeywordArg3.py ## Fehlerhafter Aufruf in Zeile 8: -p = Point(x=3, y=4, z=4) +p = Point(x=3, y=4, z=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.err b/python/test-data-2.0/failing/testWrongKeywordArg4.err index eee45903..fb120ce1 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg4.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg4.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "testWrongKeywordArg4.py", line 4, in + File "test-data-2.0/failing/testWrongKeywordArg4.py", line 4, in foo(kw=1, x=4) WyppTypeError: unbekanntes Schlüsselwort-Argument Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`. -## Datei testWrongKeywordArg4.py +## Datei test-data-2.0/failing/testWrongKeywordArg4.py ## Fehlerhafter Aufruf in Zeile 4: -foo(kw=1, x=4) +foo(kw=1, x=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.err b/python/test-data-2.0/failing/testWrongKeywordArg5.err index 51aa153f..18dcf6d7 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg5.err +++ b/python/test-data-2.0/failing/testWrongKeywordArg5.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testWrongKeywordArg5.py", line 4, in + File "test-data-2.0/failing/testWrongKeywordArg5.py", line 4, in foo() WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 1 Argument. Gegeben: keine Argumente -## Datei testWrongKeywordArg5.py +## Datei test-data-2.0/failing/testWrongKeywordArg5.py ## Aufruf in Zeile 4: -foo() +foo() \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.err b/python/test-data-2.0/failing/testWrongNumOfArguments.err index 74bb5cf3..ad2144e7 100644 --- a/python/test-data-2.0/failing/testWrongNumOfArguments.err +++ b/python/test-data-2.0/failing/testWrongNumOfArguments.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testWrongNumOfArguments.py", line 4, in + File "test-data-2.0/failing/testWrongNumOfArguments.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei testWrongNumOfArguments.py +## Datei test-data-2.0/failing/testWrongNumOfArguments.py ## Aufruf in Zeile 4: -foo(1) +foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.err b/python/test-data-2.0/failing/testWrongNumOfArguments2.err index 91506c35..9ef0bb54 100644 --- a/python/test-data-2.0/failing/testWrongNumOfArguments2.err +++ b/python/test-data-2.0/failing/testWrongNumOfArguments2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "testWrongNumOfArguments2.py", line 6, in + File "test-data-2.0/failing/testWrongNumOfArguments2.py", line 6, in c.foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Methode `foo` der Klasse `C` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei testWrongNumOfArguments2.py +## Datei test-data-2.0/failing/testWrongNumOfArguments2.py ## Aufruf in Zeile 6: -c.foo(1) +c.foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/failing/wrong-caused-by.err b/python/test-data-2.0/failing/wrong-caused-by.err index 50f93b92..5077df00 100644 --- a/python/test-data-2.0/failing/wrong-caused-by.err +++ b/python/test-data-2.0/failing/wrong-caused-by.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "wrong-caused-by.py", line 31, in + File "test-data-2.0/failing/wrong-caused-by.py", line 31, in mainStreetM.turnIntoStreet(redCarM) WyppTypeError: CarM(licensePlate='OG PY 123', color='rot') @@ -7,11 +7,11 @@ 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 wrong-caused-by.py +## Datei test-data-2.0/failing/wrong-caused-by.py ## Fehlerhafter Aufruf in Zeile 31: mainStreetM.turnIntoStreet(redCarM) ## Typ deklariert in Zeile 10: - def turnIntoStreet(self: StreetM, car: Car) -> None: + def turnIntoStreet(self: StreetM, car: Car) -> None: \ No newline at end of file From 10de15ca96d678dc97d642ad007c29fb29fad644 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 17:51:05 +0200 Subject: [PATCH 45/56] moved tests from failing to extras --- python/fileTests-2.0.py | 4 +-- python/src/errors.py | 11 +++++++ python/src/i18n.py | 5 ++- python/src/instrument.py | 30 +++++++++++------- .../declared-at-missing.err | 0 .../declared-at-missing.err-3.10 | 0 .../declared-at-missing.err-3.11 | 0 .../declared-at-missing.err-3.12 | 4 +-- .../declared-at-missing.out | 0 .../declared-at-missing.py | 0 python/test-data-2.0/extras/invalidRecord.err | 18 +++++++++++ .../invalidRecord.out} | 0 python/test-data-2.0/extras/invalidRecord.py | 4 +++ .../{failing => extras}/invalidType.err | 4 +-- .../invalidType.out} | 0 .../{failing => extras}/invalidType.py | 0 .../{failing => extras}/invalidType2.err | 2 +- .../invalidType2.out} | 0 .../{failing => extras}/invalidType2.py | 0 .../{failing => extras}/invalidType3.err | 4 +-- .../invalidType3.out} | 0 .../{failing => extras}/invalidType3.py | 0 .../{failing => extras}/invalidType4.err | 4 +-- .../main.out => extras/invalidType4.out} | 0 .../{failing => extras}/invalidType4.py | 0 .../{failing => extras}/main.err | 2 +- .../testABCMeta.out => extras/main.out} | 0 .../test-data-2.0/{failing => extras}/main.py | 0 .../{failing => extras}/testABCMeta.err | 2 +- .../{failing => extras}/testABCMeta.err-3.10 | 0 .../{failing => extras}/testABCMeta.err-3.11 | 0 .../testABCMeta.out} | 0 .../{failing => extras}/testABCMeta.py | 0 .../testArgs_ok.err} | 0 python/test-data-2.0/extras/testArgs_ok.out | 1 + .../{failing => extras}/testArgs_ok.py | 0 .../testCheck_ok.err} | 0 python/test-data-2.0/extras/testCheck_ok.out | 5 +++ .../{failing => extras}/testCheck_ok.py | 0 .../testDeepEqBug_ok.err} | 0 .../test-data-2.0/extras/testDeepEqBug_ok.out | 2 ++ .../{failing => extras}/testDeepEqBug_ok.py | 0 .../{failing => extras}/testForwardRef2.err | 4 +-- .../testForwardRef2.out} | 0 .../{failing => extras}/testForwardRef2.py | 0 .../{failing => extras}/testForwardRef4.err | 0 .../testForwardRef4.err-3.10 | 0 .../testForwardRef4.err-3.11 | 0 .../testForwardRef4.err-3.12 | 4 +-- .../testForwardRef4.out} | 0 .../{failing => extras}/testForwardRef4.py | 0 .../{failing => extras}/testForwardRef5.err | 0 .../testForwardRef5.err-3.10 | 0 .../testForwardRef5.err-3.11 | 0 .../testForwardRef5.err-3.12 | 4 +-- .../testForwardRef5.out} | 0 .../{failing => extras}/testForwardRef5.py | 0 .../testFunEq_ok.err} | 0 python/test-data-2.0/extras/testFunEq_ok.out | 2 ++ .../{failing => extras}/testFunEq_ok.py | 0 .../{failing => extras}/testGetSource.err | 4 +-- .../testGetSource.out} | 0 .../{failing => extras}/testGetSource.py | 0 .../testHintParentheses1.err | 4 +-- .../testHintParentheses1.out} | 0 .../testHintParentheses1.py | 0 .../testHintParentheses2.err | 4 +-- .../testHintParentheses2.out} | 0 .../testHintParentheses2.py | 0 .../testHintParentheses3.err | 4 +-- .../testHintParentheses3.out} | 0 .../testHintParentheses3.py | 0 .../{failing => extras}/testImpossible.err | 2 +- .../testImpossible.out} | 0 .../{failing => extras}/testImpossible.py | 0 .../test-data-2.0/extras/testIndexError.err | 6 ++++ .../testIndexError.out} | 0 .../{failing => extras}/testIndexError.py | 0 .../testInvalidLiteral.err | 4 +-- .../testInvalidLiteral.out} | 0 .../{failing => extras}/testInvalidLiteral.py | 0 .../testIterable7_ok.err} | 0 .../{failing => extras}/testIterable7_ok.out | 0 .../{failing => extras}/testIterable7_ok.py | 0 .../testIterableImplicitAny.err | 4 +-- .../testIterableImplicitAny.out | 0 .../testIterableImplicitAny.py | 0 .../{failing => extras}/testLiteral1.err | 4 +-- .../testLiteral1.out} | 0 .../{failing => extras}/testLiteral1.py | 0 .../{failing => extras}/testLockFactory.err | 4 +-- .../testLockFactory.out} | 0 .../{failing => extras}/testLockFactory.py | 0 .../{failing => extras}/testLockFactory2.err | 4 +-- .../testLockFactory2.out} | 0 .../{failing => extras}/testLockFactory2.py | 0 .../testLockFactory_ok.err} | 0 .../testLockFactory_ok.out | 0 .../{failing => extras}/testLockFactory_ok.py | 0 .../{failing => extras}/testMissingReturn.err | 6 ++-- .../testMissingReturn.out} | 0 .../{failing => extras}/testMissingReturn.py | 0 .../testOriginalTypeNames_ok.err} | 0 .../testOriginalTypeNames_ok.out | 0 .../testOriginalTypeNames_ok.py | 0 .../testRecordSetTypeForwardRef.err | 0 .../testRecordSetTypeForwardRef.err-3.10 | 0 .../testRecordSetTypeForwardRef.err-3.11 | 0 .../testRecordSetTypeForwardRef.err-3.12 | 6 ++-- .../testRecordSetTypeForwardRef.out} | 0 .../testRecordSetTypeForwardRef.py | 0 .../testRecordSetTypes.err | 0 .../testRecordSetTypes.err-3.10 | 0 .../testRecordSetTypes.err-3.11 | 0 .../testRecordSetTypes.err-3.12 | 6 ++-- .../testRecordSetTypes.out} | 0 .../{failing => extras}/testRecordSetTypes.py | 0 .../{failing => extras}/testRecordTypes.err | 0 .../testRecordTypes.err-3.12 | 4 +-- .../testRecordTypes.out} | 0 .../{failing => extras}/testRecordTypes.py | 0 .../{failing => extras}/testTodo.err | 2 +- .../testTodo.out} | 0 .../{failing => extras}/testTodo.py | 0 python/test-data-2.0/extras/testTraceback.err | 6 ++++ .../{failing => extras}/testTraceback.out | 0 .../{failing => extras}/testTraceback.py | 0 .../{failing => extras}/testTraceback2.err | 2 +- .../testTraceback2.err-3.10.0 | 0 .../testTraceback2.err-3.9 | 0 .../testTraceback2.out} | 0 .../{failing => extras}/testTraceback2.py | 0 .../{failing => extras}/testTraceback3.err | 2 +- .../testTraceback3.out} | 0 .../{failing => extras}/testTraceback3.py | 0 .../{failing => extras}/testTypes1.err | 4 +-- .../testTypes1.err-notypes | 0 .../testTypes1.out} | 0 .../{failing => extras}/testTypes1.py | 0 .../{failing => extras}/testTypesDict1.err | 0 .../testTypesDict1.out} | 0 .../{failing => extras}/testTypesDict3.err | 6 ++-- .../testTypesDict3.out} | 0 .../{failing => extras}/testTypesDict3.py | 0 .../{failing => extras}/testTypesDict4.err | 8 ++--- .../testTypesDict4.out} | 0 .../{failing => extras}/testTypesDict4.py | 0 .../testTypesHigherOrderFuns.err | 6 ++-- .../testTypesHigherOrderFuns.out | 0 .../testTypesHigherOrderFuns.py | 0 .../testTypesHigherOrderFuns3.err | 4 +-- .../testTypesHigherOrderFuns3.out} | 0 .../testTypesHigherOrderFuns3.py | 0 .../{failing => extras}/testTypesProtos1.err | 6 ++-- .../testTypesProtos1.out} | 0 .../{failing => extras}/testTypesProtos1.py | 0 .../{failing => extras}/testTypesProtos2.err | 6 ++-- .../testTypesProtos2.out} | 0 .../{failing => extras}/testTypesProtos2.py | 0 .../{failing => extras}/testTypesProtos3.err | 6 ++-- .../testTypesProtos3.out} | 0 .../{failing => extras}/testTypesProtos3.py | 0 .../{failing => extras}/testTypesProtos4.err | 8 ++--- .../{failing => extras}/testTypesProtos4.out | 0 .../{failing => extras}/testTypesProtos4.py | 0 .../{failing => extras}/testTypesProtos6.err | 12 +++---- .../testTypesProtos6.out} | 0 .../{failing => extras}/testTypesProtos6.py | 0 .../{failing => extras}/testTypesProtos7.err | 12 +++---- .../testTypesProtos7.out} | 0 .../{failing => extras}/testTypesProtos7.py | 0 .../{failing => extras}/testTypesProtos8.err | 6 ++-- .../testTypesProtos8.out} | 0 .../{failing => extras}/testTypesProtos8.py | 0 .../{failing => extras}/testTypesProtos9.err | 6 ++-- .../testTypesProtos9.out} | 0 .../{failing => extras}/testTypesProtos9.py | 0 .../testTypesRecordInheritance.err | 0 .../testTypesRecordInheritance.err-3.10 | 0 .../testTypesRecordInheritance.err-3.11 | 0 .../testTypesRecordInheritance.err-3.12 | 4 +-- .../testTypesRecordInheritance.out | 0 .../testTypesRecordInheritance.py | 0 .../{failing => extras}/testTypesReturn.err | 6 ++-- .../{failing => extras}/testTypesReturn.out | 0 .../{failing => extras}/testTypesReturn.py | 0 .../testTypesSequence1.err | 4 +-- .../testTypesSequence1.out | 0 .../{failing => extras}/testTypesSequence1.py | 0 .../testTypesSequence2.err | 4 +-- .../testTypesSequence2.out | 0 .../{failing => extras}/testTypesSequence2.py | 0 .../{failing => extras}/testTypesSet1.err | 0 .../testTypesSet1.out} | 0 .../testTypesSubclassing1.err | 6 ++-- .../testTypesSubclassing1.out} | 0 .../testTypesSubclassing1.py | 0 .../{failing => extras}/testTypesTuple1.err | 4 +-- .../{failing => extras}/testTypesTuple1.out | 0 .../{failing => extras}/testTypesTuple1.py | 0 .../testWrongKeywordArg.err | 4 +-- .../testWrongKeywordArg.out} | 0 .../testWrongKeywordArg.py | 0 .../testWrongKeywordArg2.err | 0 .../testWrongKeywordArg2.err-3.10 | 0 .../testWrongKeywordArg2.err-3.11 | 0 .../testWrongKeywordArg2.err-3.12 | 4 +-- .../testWrongKeywordArg2.out} | 0 .../testWrongKeywordArg2.py | 0 .../testWrongKeywordArg3.err | 4 +-- .../testWrongKeywordArg3.out} | 0 .../testWrongKeywordArg3.py | 0 .../testWrongKeywordArg4.err | 4 +-- .../testWrongKeywordArg4.out} | 0 .../testWrongKeywordArg4.py | 0 .../testWrongKeywordArg5.err | 4 +-- .../testWrongKeywordArg5.out} | 0 .../testWrongKeywordArg5.py | 0 .../testWrongNumOfArguments.err | 4 +-- .../testWrongNumOfArguments.out} | 0 .../testWrongNumOfArguments.py | 0 .../testWrongNumOfArguments2.err | 4 +-- .../testWrongNumOfArguments2.out} | 0 .../testWrongNumOfArguments2.py | 0 .../{failing => extras}/wrong-caused-by.err | 4 +-- .../test-data-2.0/extras/wrong-caused-by.out | 0 .../{failing => extras}/wrong-caused-by.py | 0 .../failing/.invalidType3.py.swp | Bin 12288 -> 0 bytes python/test-data-2.0/failing/testArgs_ok.out | 1 - python/test-data-2.0/failing/testCheck_ok.out | 5 --- .../failing/testDeepEqBug_ok.out | 2 -- python/test-data-2.0/failing/testFunEq_ok.out | 2 -- .../test-data-2.0/failing/testIndexError.err | 6 ---- .../test-data-2.0/failing/testTraceback.err | 6 ---- 234 files changed, 204 insertions(+), 162 deletions(-) rename python/test-data-2.0/{failing => extras}/declared-at-missing.err (100%) rename python/test-data-2.0/{failing => extras}/declared-at-missing.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/declared-at-missing.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/declared-at-missing.err-3.12 (79%) rename python/test-data-2.0/{failing => extras}/declared-at-missing.out (100%) rename python/test-data-2.0/{failing => extras}/declared-at-missing.py (100%) create mode 100644 python/test-data-2.0/extras/invalidRecord.err rename python/test-data-2.0/{failing/invalidType.out => extras/invalidRecord.out} (100%) create mode 100644 python/test-data-2.0/extras/invalidRecord.py rename python/test-data-2.0/{failing => extras}/invalidType.err (69%) rename python/test-data-2.0/{failing/invalidType2.out => extras/invalidType.out} (100%) rename python/test-data-2.0/{failing => extras}/invalidType.py (100%) rename python/test-data-2.0/{failing => extras}/invalidType2.err (62%) rename python/test-data-2.0/{failing/invalidType3.out => extras/invalidType2.out} (100%) rename python/test-data-2.0/{failing => extras}/invalidType2.py (100%) rename python/test-data-2.0/{failing => extras}/invalidType3.err (69%) rename python/test-data-2.0/{failing/invalidType4.out => extras/invalidType3.out} (100%) rename python/test-data-2.0/{failing => extras}/invalidType3.py (100%) rename python/test-data-2.0/{failing => extras}/invalidType4.err (64%) rename python/test-data-2.0/{failing/main.out => extras/invalidType4.out} (100%) rename python/test-data-2.0/{failing => extras}/invalidType4.py (100%) rename python/test-data-2.0/{failing => extras}/main.err (88%) rename python/test-data-2.0/{failing/testABCMeta.out => extras/main.out} (100%) rename python/test-data-2.0/{failing => extras}/main.py (100%) rename python/test-data-2.0/{failing => extras}/testABCMeta.err (70%) rename python/test-data-2.0/{failing => extras}/testABCMeta.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testABCMeta.err-3.11 (100%) rename python/test-data-2.0/{failing/testArgs_ok.err => extras/testABCMeta.out} (100%) rename python/test-data-2.0/{failing => extras}/testABCMeta.py (100%) rename python/test-data-2.0/{failing/testCheck_ok.err => extras/testArgs_ok.err} (100%) create mode 100644 python/test-data-2.0/extras/testArgs_ok.out rename python/test-data-2.0/{failing => extras}/testArgs_ok.py (100%) rename python/test-data-2.0/{failing/testDeepEqBug_ok.err => extras/testCheck_ok.err} (100%) create mode 100644 python/test-data-2.0/extras/testCheck_ok.out rename python/test-data-2.0/{failing => extras}/testCheck_ok.py (100%) rename python/test-data-2.0/{failing/testForwardRef2.out => extras/testDeepEqBug_ok.err} (100%) create mode 100644 python/test-data-2.0/extras/testDeepEqBug_ok.out rename python/test-data-2.0/{failing => extras}/testDeepEqBug_ok.py (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef2.err (59%) rename python/test-data-2.0/{failing/testForwardRef4.out => extras/testForwardRef2.out} (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef2.py (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef4.err (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef4.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef4.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef4.err-3.12 (56%) rename python/test-data-2.0/{failing/testForwardRef5.out => extras/testForwardRef4.out} (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef4.py (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef5.err (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef5.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef5.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef5.err-3.12 (77%) rename python/test-data-2.0/{failing/testFunEq_ok.err => extras/testForwardRef5.out} (100%) rename python/test-data-2.0/{failing => extras}/testForwardRef5.py (100%) rename python/test-data-2.0/{failing/testGetSource.out => extras/testFunEq_ok.err} (100%) create mode 100644 python/test-data-2.0/extras/testFunEq_ok.out rename python/test-data-2.0/{failing => extras}/testFunEq_ok.py (100%) rename python/test-data-2.0/{failing => extras}/testGetSource.err (81%) rename python/test-data-2.0/{failing/testHintParentheses1.out => extras/testGetSource.out} (100%) rename python/test-data-2.0/{failing => extras}/testGetSource.py (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses1.err (62%) rename python/test-data-2.0/{failing/testHintParentheses2.out => extras/testHintParentheses1.out} (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses1.py (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses2.err (60%) rename python/test-data-2.0/{failing/testHintParentheses3.out => extras/testHintParentheses2.out} (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses2.py (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses3.err (62%) rename python/test-data-2.0/{failing/testImpossible.out => extras/testHintParentheses3.out} (100%) rename python/test-data-2.0/{failing => extras}/testHintParentheses3.py (100%) rename python/test-data-2.0/{failing => extras}/testImpossible.err (72%) rename python/test-data-2.0/{failing/testIndexError.out => extras/testImpossible.out} (100%) rename python/test-data-2.0/{failing => extras}/testImpossible.py (100%) create mode 100644 python/test-data-2.0/extras/testIndexError.err rename python/test-data-2.0/{failing/testInvalidLiteral.out => extras/testIndexError.out} (100%) rename python/test-data-2.0/{failing => extras}/testIndexError.py (100%) rename python/test-data-2.0/{failing => extras}/testInvalidLiteral.err (60%) rename python/test-data-2.0/{failing/testIterable7_ok.err => extras/testInvalidLiteral.out} (100%) rename python/test-data-2.0/{failing => extras}/testInvalidLiteral.py (100%) rename python/test-data-2.0/{failing/testLiteral1.out => extras/testIterable7_ok.err} (100%) rename python/test-data-2.0/{failing => extras}/testIterable7_ok.out (100%) rename python/test-data-2.0/{failing => extras}/testIterable7_ok.py (100%) rename python/test-data-2.0/{failing => extras}/testIterableImplicitAny.err (72%) rename python/test-data-2.0/{failing => extras}/testIterableImplicitAny.out (100%) rename python/test-data-2.0/{failing => extras}/testIterableImplicitAny.py (100%) rename python/test-data-2.0/{failing => extras}/testLiteral1.err (79%) rename python/test-data-2.0/{failing/testLockFactory.out => extras/testLiteral1.out} (100%) rename python/test-data-2.0/{failing => extras}/testLiteral1.py (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory.err (74%) rename python/test-data-2.0/{failing/testLockFactory2.out => extras/testLockFactory.out} (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory.py (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory2.err (73%) rename python/test-data-2.0/{failing/testLockFactory_ok.err => extras/testLockFactory2.out} (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory2.py (100%) rename python/test-data-2.0/{failing/testMissingReturn.out => extras/testLockFactory_ok.err} (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory_ok.out (100%) rename python/test-data-2.0/{failing => extras}/testLockFactory_ok.py (100%) rename python/test-data-2.0/{failing => extras}/testMissingReturn.err (68%) rename python/test-data-2.0/{failing/testOriginalTypeNames_ok.err => extras/testMissingReturn.out} (100%) rename python/test-data-2.0/{failing => extras}/testMissingReturn.py (100%) rename python/test-data-2.0/{failing/testRecordSetTypeForwardRef.out => extras/testOriginalTypeNames_ok.err} (100%) rename python/test-data-2.0/{failing => extras}/testOriginalTypeNames_ok.out (100%) rename python/test-data-2.0/{failing => extras}/testOriginalTypeNames_ok.py (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypeForwardRef.err (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypeForwardRef.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypeForwardRef.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypeForwardRef.err-3.12 (60%) rename python/test-data-2.0/{failing/testRecordSetTypes.out => extras/testRecordSetTypeForwardRef.out} (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypeForwardRef.py (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypes.err (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypes.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypes.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypes.err-3.12 (64%) rename python/test-data-2.0/{failing/testRecordTypes.out => extras/testRecordSetTypes.out} (100%) rename python/test-data-2.0/{failing => extras}/testRecordSetTypes.py (100%) rename python/test-data-2.0/{failing => extras}/testRecordTypes.err (100%) rename python/test-data-2.0/{failing => extras}/testRecordTypes.err-3.12 (74%) rename python/test-data-2.0/{failing/testTodo.out => extras/testRecordTypes.out} (100%) rename python/test-data-2.0/{failing => extras}/testRecordTypes.py (100%) rename python/test-data-2.0/{failing => extras}/testTodo.err (69%) rename python/test-data-2.0/{failing/testTraceback2.out => extras/testTodo.out} (100%) rename python/test-data-2.0/{failing => extras}/testTodo.py (100%) create mode 100644 python/test-data-2.0/extras/testTraceback.err rename python/test-data-2.0/{failing => extras}/testTraceback.out (100%) rename python/test-data-2.0/{failing => extras}/testTraceback.py (100%) rename python/test-data-2.0/{failing => extras}/testTraceback2.err (74%) rename python/test-data-2.0/{failing => extras}/testTraceback2.err-3.10.0 (100%) rename python/test-data-2.0/{failing => extras}/testTraceback2.err-3.9 (100%) rename python/test-data-2.0/{failing/testTraceback3.out => extras/testTraceback2.out} (100%) rename python/test-data-2.0/{failing => extras}/testTraceback2.py (100%) rename python/test-data-2.0/{failing => extras}/testTraceback3.err (57%) rename python/test-data-2.0/{failing/testTypes1.out => extras/testTraceback3.out} (100%) rename python/test-data-2.0/{failing => extras}/testTraceback3.py (100%) rename python/test-data-2.0/{failing => extras}/testTypes1.err (73%) rename python/test-data-2.0/{failing => extras}/testTypes1.err-notypes (100%) rename python/test-data-2.0/{failing/testTypesDict1.out => extras/testTypes1.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypes1.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesDict1.err (100%) rename python/test-data-2.0/{failing/testTypesDict3.out => extras/testTypesDict1.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesDict3.err (69%) rename python/test-data-2.0/{failing/testTypesDict4.out => extras/testTypesDict3.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesDict3.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesDict4.err (64%) rename python/test-data-2.0/{failing/testTypesHigherOrderFuns3.out => extras/testTypesDict4.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesDict4.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesHigherOrderFuns.err (68%) rename python/test-data-2.0/{failing => extras}/testTypesHigherOrderFuns.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesHigherOrderFuns.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesHigherOrderFuns3.err (78%) rename python/test-data-2.0/{failing/testTypesProtos1.out => extras/testTypesHigherOrderFuns3.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesHigherOrderFuns3.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos1.err (68%) rename python/test-data-2.0/{failing/testTypesProtos2.out => extras/testTypesProtos1.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos1.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos2.err (68%) rename python/test-data-2.0/{failing/testTypesProtos3.out => extras/testTypesProtos2.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos2.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos3.err (69%) rename python/test-data-2.0/{failing/testTypesProtos6.out => extras/testTypesProtos3.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos3.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos4.err (65%) rename python/test-data-2.0/{failing => extras}/testTypesProtos4.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos4.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos6.err (56%) rename python/test-data-2.0/{failing/testTypesProtos7.out => extras/testTypesProtos6.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos6.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos7.err (55%) rename python/test-data-2.0/{failing/testTypesProtos8.out => extras/testTypesProtos7.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos7.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos8.err (67%) rename python/test-data-2.0/{failing/testTypesProtos9.out => extras/testTypesProtos8.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos8.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos9.err (67%) rename python/test-data-2.0/{failing/testTypesSet1.out => extras/testTypesProtos9.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesProtos9.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.err (100%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.err-3.12 (70%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesRecordInheritance.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesReturn.err (70%) rename python/test-data-2.0/{failing => extras}/testTypesReturn.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesReturn.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesSequence1.err (72%) rename python/test-data-2.0/{failing => extras}/testTypesSequence1.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesSequence1.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesSequence2.err (74%) rename python/test-data-2.0/{failing => extras}/testTypesSequence2.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesSequence2.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesSet1.err (100%) rename python/test-data-2.0/{failing/testTypesSubclassing1.out => extras/testTypesSet1.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesSubclassing1.err (68%) rename python/test-data-2.0/{failing/testWrongKeywordArg.out => extras/testTypesSubclassing1.out} (100%) rename python/test-data-2.0/{failing => extras}/testTypesSubclassing1.py (100%) rename python/test-data-2.0/{failing => extras}/testTypesTuple1.err (72%) rename python/test-data-2.0/{failing => extras}/testTypesTuple1.out (100%) rename python/test-data-2.0/{failing => extras}/testTypesTuple1.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg.err (61%) rename python/test-data-2.0/{failing/testWrongKeywordArg2.out => extras/testWrongKeywordArg.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg2.err (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg2.err-3.10 (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg2.err-3.11 (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg2.err-3.12 (67%) rename python/test-data-2.0/{failing/testWrongKeywordArg3.out => extras/testWrongKeywordArg2.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg2.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg3.err (67%) rename python/test-data-2.0/{failing/testWrongKeywordArg4.out => extras/testWrongKeywordArg3.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg3.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg4.err (62%) rename python/test-data-2.0/{failing/testWrongKeywordArg5.out => extras/testWrongKeywordArg4.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg4.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg5.err (58%) rename python/test-data-2.0/{failing/testWrongNumOfArguments.out => extras/testWrongKeywordArg5.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongKeywordArg5.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongNumOfArguments.err (57%) rename python/test-data-2.0/{failing/testWrongNumOfArguments2.out => extras/testWrongNumOfArguments.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongNumOfArguments.py (100%) rename python/test-data-2.0/{failing => extras}/testWrongNumOfArguments2.err (59%) rename python/test-data-2.0/{failing/wrong-caused-by.out => extras/testWrongNumOfArguments2.out} (100%) rename python/test-data-2.0/{failing => extras}/testWrongNumOfArguments2.py (100%) rename python/test-data-2.0/{failing => extras}/wrong-caused-by.err (79%) create mode 100644 python/test-data-2.0/extras/wrong-caused-by.out rename python/test-data-2.0/{failing => extras}/wrong-caused-by.py (100%) delete mode 100644 python/test-data-2.0/failing/.invalidType3.py.swp delete mode 100644 python/test-data-2.0/failing/testArgs_ok.out delete mode 100644 python/test-data-2.0/failing/testCheck_ok.out delete mode 100644 python/test-data-2.0/failing/testDeepEqBug_ok.out delete mode 100644 python/test-data-2.0/failing/testFunEq_ok.out delete mode 100644 python/test-data-2.0/failing/testIndexError.err delete mode 100644 python/test-data-2.0/failing/testTraceback.err diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index cfdbc6c0..662a9cb4 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -2,12 +2,10 @@ from fileTestsLib import * directories = [Path("test-data-2.0/basics"), - Path("test-data-2.0/extras"), - Path("test-data-2.0/failing")] + Path("test-data-2.0/extras")] #directories = [Path("test-data-2.0/basics")] #directories = [Path("test-data-2.0/extras")] -#directories = [Path("test-data-2.0/failing")] for d in directories: for file in d.iterdir(): diff --git a/python/src/errors.py b/python/src/errors.py index e36e844a..670cec5f 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -91,6 +91,17 @@ def unknownKeywordArgument(callableName: location.CallableName, callLoc: Optiona lines.append(renderLoc(callLoc)) 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: + lines.append(f'## {i18n.tr("File")} {loc.filename}') + lines.append(f'## {i18n.tr("Line")} {loc.startLine}:\n') + lines.append(renderLoc(loc)) + raise WyppTypeError('\n'.join(lines)) + @staticmethod def resultError(callableName: location.CallableName, resultTypeLoc: Optional[location.Loc], resultTy: Any, returnLoc: Optional[location.Loc], givenValue: Any, diff --git a/python/src/i18n.py b/python/src/i18n.py index 82a644a9..a9482ef0 100644 --- a/python/src/i18n.py +++ b/python/src/i18n.py @@ -81,6 +81,7 @@ def tr(key: str, **kws) -> str: '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', @@ -137,7 +138,9 @@ def tr(key: str, **kws) -> str: '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}`.' + 'Konstruktor des Records `{cls}` akzeptiert kein Schlüsselwort-Argument `{name}`.', + + 'invalid record definition': 'ungültige Record-Definition' } def expectingNoReturn(cn: location.CallableName) -> str: diff --git a/python/src/instrument.py b/python/src/instrument.py index f6e5acc6..68178e6d 100644 --- a/python/src/instrument.py +++ b/python/src/instrument.py @@ -11,6 +11,8 @@ import utils from myLogging import * from contextlib import contextmanager +import errors +import location def parseExp(s: str) -> ast.expr: match ast.parse(s): @@ -34,7 +36,8 @@ def transferLocs(old: ast.stmt | ast.expr, new: ast.stmt | ast.expr) -> Any: new.end_col_offset = old.end_col_offset return new -def transformDecorator(e: ast.expr) -> ast.expr: +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) @@ -45,28 +48,27 @@ def transformDecorator(e: ast.expr) -> ast.expr: case [ast.keyword('mutable', ast.Constant(False))]: return transferLocs(e, Configs.immutableRecordConfig) case _: - # print(ast.dump(e)) FIXME: proper error message - raise ValueError(f'Invalid record config') + 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]) -> ast.stmt: +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) for s in body] + 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) for s in body] + 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) for s in body] - newDecoratorList = [transformDecorator(e) for e in decoratorList] + 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 _: @@ -80,10 +82,10 @@ def isImport(t: ast.stmt) -> bool: importWrapTypecheck = ast.parse("from wypp import wrapTypecheck", mode="exec").body[0] -def transformModule(m: ast.Module | ast.Expression | ast.Interactive) -> ast.Module | ast.Expression | ast.Interactive: +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) for stmt in body] + 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 @@ -105,7 +107,13 @@ def source_to_code( else: source = decode_source(data) tree = utils._call_with_frames_removed(ast.parse, source, path, "exec") - tree = transformModule(tree) + 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( diff --git a/python/test-data-2.0/failing/declared-at-missing.err b/python/test-data-2.0/extras/declared-at-missing.err similarity index 100% rename from python/test-data-2.0/failing/declared-at-missing.err rename to python/test-data-2.0/extras/declared-at-missing.err diff --git a/python/test-data-2.0/failing/declared-at-missing.err-3.10 b/python/test-data-2.0/extras/declared-at-missing.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/declared-at-missing.err-3.10 rename to python/test-data-2.0/extras/declared-at-missing.err-3.10 diff --git a/python/test-data-2.0/failing/declared-at-missing.err-3.11 b/python/test-data-2.0/extras/declared-at-missing.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/declared-at-missing.err-3.11 rename to python/test-data-2.0/extras/declared-at-missing.err-3.11 diff --git a/python/test-data-2.0/failing/declared-at-missing.err-3.12 b/python/test-data-2.0/extras/declared-at-missing.err-3.12 similarity index 79% rename from python/test-data-2.0/failing/declared-at-missing.err-3.12 rename to python/test-data-2.0/extras/declared-at-missing.err-3.12 index b73543d8..0e0d4aca 100644 --- a/python/test-data-2.0/failing/declared-at-missing.err-3.12 +++ b/python/test-data-2.0/extras/declared-at-missing.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/declared-at-missing.py", line 22, in + File "test-data-2.0/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 test-data-2.0/failing/declared-at-missing.py +## Datei test-data-2.0/extras/declared-at-missing.py ## Fehlerhafter Aufruf in Zeile 22: semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) diff --git a/python/test-data-2.0/failing/declared-at-missing.out b/python/test-data-2.0/extras/declared-at-missing.out similarity index 100% rename from python/test-data-2.0/failing/declared-at-missing.out rename to python/test-data-2.0/extras/declared-at-missing.out diff --git a/python/test-data-2.0/failing/declared-at-missing.py b/python/test-data-2.0/extras/declared-at-missing.py similarity index 100% rename from python/test-data-2.0/failing/declared-at-missing.py rename to python/test-data-2.0/extras/declared-at-missing.py diff --git a/python/test-data-2.0/extras/invalidRecord.err b/python/test-data-2.0/extras/invalidRecord.err new file mode 100644 index 00000000..8c9f2f7d --- /dev/null +++ b/python/test-data-2.0/extras/invalidRecord.err @@ -0,0 +1,18 @@ +Traceback (most recent call last): + File "src/instrument.py", line 116, in source_to_code + tree = transformModule(tree, pathStr) + File "src/instrument.py", line 88, in transformModule + newStmts = [transformStmt(stmt, outerClassName=None, path=path) for stmt in body] + File "src/instrument.py", line 71, in transformStmt + newDecoratorList = [transformDecorator(e, path=path) for e in decoratorList] + File "src/instrument.py", line 51, in transformDecorator + raise errors.WyppTypeError.invalidRecordAnnotation(loc) + File "src/errors.py", line 103, in invalidRecordAnnotation + raise WyppTypeError('\n'.join(lines)) + +WyppTypeError: ungültige Record-Definition + +## Datei test-data-2.0/extras/invalidRecord.py +## Zeile 1: + +@record(mut=True) diff --git a/python/test-data-2.0/failing/invalidType.out b/python/test-data-2.0/extras/invalidRecord.out similarity index 100% rename from python/test-data-2.0/failing/invalidType.out rename to python/test-data-2.0/extras/invalidRecord.out diff --git a/python/test-data-2.0/extras/invalidRecord.py b/python/test-data-2.0/extras/invalidRecord.py new file mode 100644 index 00000000..9b14e61b --- /dev/null +++ b/python/test-data-2.0/extras/invalidRecord.py @@ -0,0 +1,4 @@ +@record(mut=True) +class C: + pass + diff --git a/python/test-data-2.0/failing/invalidType.err b/python/test-data-2.0/extras/invalidType.err similarity index 69% rename from python/test-data-2.0/failing/invalidType.err rename to python/test-data-2.0/extras/invalidType.err index 699464d7..b940bd19 100644 --- a/python/test-data-2.0/failing/invalidType.err +++ b/python/test-data-2.0/extras/invalidType.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/invalidType.py", line 9, in + File "test-data-2.0/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 test-data-2.0/failing/invalidType.py +## Datei test-data-2.0/extras/invalidType.py ## Typ deklariert in Zeile 6: def foo() -> Union(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType2.out b/python/test-data-2.0/extras/invalidType.out similarity index 100% rename from python/test-data-2.0/failing/invalidType2.out rename to python/test-data-2.0/extras/invalidType.out diff --git a/python/test-data-2.0/failing/invalidType.py b/python/test-data-2.0/extras/invalidType.py similarity index 100% rename from python/test-data-2.0/failing/invalidType.py rename to python/test-data-2.0/extras/invalidType.py diff --git a/python/test-data-2.0/failing/invalidType2.err b/python/test-data-2.0/extras/invalidType2.err similarity index 62% rename from python/test-data-2.0/failing/invalidType2.err rename to python/test-data-2.0/extras/invalidType2.err index cd55d413..3da0f076 100644 --- a/python/test-data-2.0/failing/invalidType2.err +++ b/python/test-data-2.0/extras/invalidType2.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/failing/invalidType2.py", line 5, in + File "test-data-2.0/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-2.0/failing/invalidType3.out b/python/test-data-2.0/extras/invalidType2.out similarity index 100% rename from python/test-data-2.0/failing/invalidType3.out rename to python/test-data-2.0/extras/invalidType2.out diff --git a/python/test-data-2.0/failing/invalidType2.py b/python/test-data-2.0/extras/invalidType2.py similarity index 100% rename from python/test-data-2.0/failing/invalidType2.py rename to python/test-data-2.0/extras/invalidType2.py diff --git a/python/test-data-2.0/failing/invalidType3.err b/python/test-data-2.0/extras/invalidType3.err similarity index 69% rename from python/test-data-2.0/failing/invalidType3.err rename to python/test-data-2.0/extras/invalidType3.err index dbe1d40c..119af10f 100644 --- a/python/test-data-2.0/failing/invalidType3.err +++ b/python/test-data-2.0/extras/invalidType3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/invalidType3.py", line 9, in + File "test-data-2.0/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 test-data-2.0/failing/invalidType3.py +## Datei test-data-2.0/extras/invalidType3.py ## Typ deklariert in Zeile 6: def foo() -> Optional(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/failing/invalidType4.out b/python/test-data-2.0/extras/invalidType3.out similarity index 100% rename from python/test-data-2.0/failing/invalidType4.out rename to python/test-data-2.0/extras/invalidType3.out diff --git a/python/test-data-2.0/failing/invalidType3.py b/python/test-data-2.0/extras/invalidType3.py similarity index 100% rename from python/test-data-2.0/failing/invalidType3.py rename to python/test-data-2.0/extras/invalidType3.py diff --git a/python/test-data-2.0/failing/invalidType4.err b/python/test-data-2.0/extras/invalidType4.err similarity index 64% rename from python/test-data-2.0/failing/invalidType4.err rename to python/test-data-2.0/extras/invalidType4.err index 31f82ddf..896b8ae2 100644 --- a/python/test-data-2.0/failing/invalidType4.err +++ b/python/test-data-2.0/extras/invalidType4.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/failing/invalidType4.py", line 9, in + File "test-data-2.0/extras/invalidType4.py", line 9, in foo() WyppTypeError: ungültiger Typ `Optional[list[int], list[float]]` -## Datei test-data-2.0/failing/invalidType4.py +## Datei test-data-2.0/extras/invalidType4.py ## Typ deklariert in Zeile 6: def foo() -> Optional[list[int], list[float]]: \ No newline at end of file diff --git a/python/test-data-2.0/failing/main.out b/python/test-data-2.0/extras/invalidType4.out similarity index 100% rename from python/test-data-2.0/failing/main.out rename to python/test-data-2.0/extras/invalidType4.out diff --git a/python/test-data-2.0/failing/invalidType4.py b/python/test-data-2.0/extras/invalidType4.py similarity index 100% rename from python/test-data-2.0/failing/invalidType4.py rename to python/test-data-2.0/extras/invalidType4.py diff --git a/python/test-data-2.0/failing/main.err b/python/test-data-2.0/extras/main.err similarity index 88% rename from python/test-data-2.0/failing/main.err rename to python/test-data-2.0/extras/main.err index a0a978fd..cb3f5dd2 100644 --- a/python/test-data-2.0/failing/main.err +++ b/python/test-data-2.0/extras/main.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/main.py", line 4, in + File "test-data-2.0/extras/main.py", line 4, in print(mod.foo(1)) File "test-data-2.0/modules/B/mod.py", line 5, in foo return bar(i) diff --git a/python/test-data-2.0/failing/testABCMeta.out b/python/test-data-2.0/extras/main.out similarity index 100% rename from python/test-data-2.0/failing/testABCMeta.out rename to python/test-data-2.0/extras/main.out diff --git a/python/test-data-2.0/failing/main.py b/python/test-data-2.0/extras/main.py similarity index 100% rename from python/test-data-2.0/failing/main.py rename to python/test-data-2.0/extras/main.py diff --git a/python/test-data-2.0/failing/testABCMeta.err b/python/test-data-2.0/extras/testABCMeta.err similarity index 70% rename from python/test-data-2.0/failing/testABCMeta.err rename to python/test-data-2.0/extras/testABCMeta.err index 41e956ea..1ed256d7 100644 --- a/python/test-data-2.0/failing/testABCMeta.err +++ b/python/test-data-2.0/extras/testABCMeta.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testABCMeta.py", line 28, in + File "test-data-2.0/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' \ No newline at end of file diff --git a/python/test-data-2.0/failing/testABCMeta.err-3.10 b/python/test-data-2.0/extras/testABCMeta.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testABCMeta.err-3.10 rename to python/test-data-2.0/extras/testABCMeta.err-3.10 diff --git a/python/test-data-2.0/failing/testABCMeta.err-3.11 b/python/test-data-2.0/extras/testABCMeta.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testABCMeta.err-3.11 rename to python/test-data-2.0/extras/testABCMeta.err-3.11 diff --git a/python/test-data-2.0/failing/testArgs_ok.err b/python/test-data-2.0/extras/testABCMeta.out similarity index 100% rename from python/test-data-2.0/failing/testArgs_ok.err rename to python/test-data-2.0/extras/testABCMeta.out diff --git a/python/test-data-2.0/failing/testABCMeta.py b/python/test-data-2.0/extras/testABCMeta.py similarity index 100% rename from python/test-data-2.0/failing/testABCMeta.py rename to python/test-data-2.0/extras/testABCMeta.py diff --git a/python/test-data-2.0/failing/testCheck_ok.err b/python/test-data-2.0/extras/testArgs_ok.err similarity index 100% rename from python/test-data-2.0/failing/testCheck_ok.err rename to python/test-data-2.0/extras/testArgs_ok.err diff --git a/python/test-data-2.0/extras/testArgs_ok.out b/python/test-data-2.0/extras/testArgs_ok.out new file mode 100644 index 00000000..0d379756 --- /dev/null +++ b/python/test-data-2.0/extras/testArgs_ok.out @@ -0,0 +1 @@ +['test-data-2.0/extras/testArgs_ok.py', 'ARG_1', 'ARG_2'] diff --git a/python/test-data-2.0/failing/testArgs_ok.py b/python/test-data-2.0/extras/testArgs_ok.py similarity index 100% rename from python/test-data-2.0/failing/testArgs_ok.py rename to python/test-data-2.0/extras/testArgs_ok.py diff --git a/python/test-data-2.0/failing/testDeepEqBug_ok.err b/python/test-data-2.0/extras/testCheck_ok.err similarity index 100% rename from python/test-data-2.0/failing/testDeepEqBug_ok.err rename to python/test-data-2.0/extras/testCheck_ok.err diff --git a/python/test-data-2.0/extras/testCheck_ok.out b/python/test-data-2.0/extras/testCheck_ok.out new file mode 100644 index 00000000..19eb0013 --- /dev/null +++ b/python/test-data-2.0/extras/testCheck_ok.out @@ -0,0 +1,5 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.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-2.0/failing/testCheck_ok.py b/python/test-data-2.0/extras/testCheck_ok.py similarity index 100% rename from python/test-data-2.0/failing/testCheck_ok.py rename to python/test-data-2.0/extras/testCheck_ok.py diff --git a/python/test-data-2.0/failing/testForwardRef2.out b/python/test-data-2.0/extras/testDeepEqBug_ok.err similarity index 100% rename from python/test-data-2.0/failing/testForwardRef2.out rename to python/test-data-2.0/extras/testDeepEqBug_ok.err diff --git a/python/test-data-2.0/extras/testDeepEqBug_ok.out b/python/test-data-2.0/extras/testDeepEqBug_ok.out new file mode 100644 index 00000000..c162a807 --- /dev/null +++ b/python/test-data-2.0/extras/testDeepEqBug_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testDeepEqBug_ok.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-2.0/failing/testDeepEqBug_ok.py b/python/test-data-2.0/extras/testDeepEqBug_ok.py similarity index 100% rename from python/test-data-2.0/failing/testDeepEqBug_ok.py rename to python/test-data-2.0/extras/testDeepEqBug_ok.py diff --git a/python/test-data-2.0/failing/testForwardRef2.err b/python/test-data-2.0/extras/testForwardRef2.err similarity index 59% rename from python/test-data-2.0/failing/testForwardRef2.err rename to python/test-data-2.0/extras/testForwardRef2.err index ac96f0c0..45795381 100644 --- a/python/test-data-2.0/failing/testForwardRef2.err +++ b/python/test-data-2.0/extras/testForwardRef2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testForwardRef2.py", line 10, in + File "test-data-2.0/extras/testForwardRef2.py", line 10, in t = Test(FooX()) WyppTypeError: ungültiger Typ `Foo` -## Datei test-data-2.0/failing/testForwardRef2.py +## Datei test-data-2.0/extras/testForwardRef2.py ## Typ deklariert in Zeile 2: def __init__(self, foo: 'Foo'): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testForwardRef4.out b/python/test-data-2.0/extras/testForwardRef2.out similarity index 100% rename from python/test-data-2.0/failing/testForwardRef4.out rename to python/test-data-2.0/extras/testForwardRef2.out diff --git a/python/test-data-2.0/failing/testForwardRef2.py b/python/test-data-2.0/extras/testForwardRef2.py similarity index 100% rename from python/test-data-2.0/failing/testForwardRef2.py rename to python/test-data-2.0/extras/testForwardRef2.py diff --git a/python/test-data-2.0/failing/testForwardRef4.err b/python/test-data-2.0/extras/testForwardRef4.err similarity index 100% rename from python/test-data-2.0/failing/testForwardRef4.err rename to python/test-data-2.0/extras/testForwardRef4.err diff --git a/python/test-data-2.0/failing/testForwardRef4.err-3.10 b/python/test-data-2.0/extras/testForwardRef4.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testForwardRef4.err-3.10 rename to python/test-data-2.0/extras/testForwardRef4.err-3.10 diff --git a/python/test-data-2.0/failing/testForwardRef4.err-3.11 b/python/test-data-2.0/extras/testForwardRef4.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testForwardRef4.err-3.11 rename to python/test-data-2.0/extras/testForwardRef4.err-3.11 diff --git a/python/test-data-2.0/failing/testForwardRef4.err-3.12 b/python/test-data-2.0/extras/testForwardRef4.err-3.12 similarity index 56% rename from python/test-data-2.0/failing/testForwardRef4.err-3.12 rename to python/test-data-2.0/extras/testForwardRef4.err-3.12 index 6805c80f..85fe5efe 100644 --- a/python/test-data-2.0/failing/testForwardRef4.err-3.12 +++ b/python/test-data-2.0/extras/testForwardRef4.err-3.12 @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testForwardRef4.py", line 11, in + File "test-data-2.0/extras/testForwardRef4.py", line 11, in t = Test(Foo()) WyppTypeError: ungültiger Typ `FooX` -## Datei test-data-2.0/failing/testForwardRef4.py +## Datei test-data-2.0/extras/testForwardRef4.py ## Typ deklariert in Zeile 5: foo: 'FooX' \ No newline at end of file diff --git a/python/test-data-2.0/failing/testForwardRef5.out b/python/test-data-2.0/extras/testForwardRef4.out similarity index 100% rename from python/test-data-2.0/failing/testForwardRef5.out rename to python/test-data-2.0/extras/testForwardRef4.out diff --git a/python/test-data-2.0/failing/testForwardRef4.py b/python/test-data-2.0/extras/testForwardRef4.py similarity index 100% rename from python/test-data-2.0/failing/testForwardRef4.py rename to python/test-data-2.0/extras/testForwardRef4.py diff --git a/python/test-data-2.0/failing/testForwardRef5.err b/python/test-data-2.0/extras/testForwardRef5.err similarity index 100% rename from python/test-data-2.0/failing/testForwardRef5.err rename to python/test-data-2.0/extras/testForwardRef5.err diff --git a/python/test-data-2.0/failing/testForwardRef5.err-3.10 b/python/test-data-2.0/extras/testForwardRef5.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testForwardRef5.err-3.10 rename to python/test-data-2.0/extras/testForwardRef5.err-3.10 diff --git a/python/test-data-2.0/failing/testForwardRef5.err-3.11 b/python/test-data-2.0/extras/testForwardRef5.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testForwardRef5.err-3.11 rename to python/test-data-2.0/extras/testForwardRef5.err-3.11 diff --git a/python/test-data-2.0/failing/testForwardRef5.err-3.12 b/python/test-data-2.0/extras/testForwardRef5.err-3.12 similarity index 77% rename from python/test-data-2.0/failing/testForwardRef5.err-3.12 rename to python/test-data-2.0/extras/testForwardRef5.err-3.12 index 931c9995..fb1e0be5 100644 --- a/python/test-data-2.0/failing/testForwardRef5.err-3.12 +++ b/python/test-data-2.0/extras/testForwardRef5.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testForwardRef5.py", line 22, in + File "test-data-2.0/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 test-data-2.0/failing/testForwardRef5.py +## Datei test-data-2.0/extras/testForwardRef5.py ## Fehlerhafter Aufruf in Zeile 22: garage = Garage(cars=[Car(color='red'), "Not A Car"]) diff --git a/python/test-data-2.0/failing/testFunEq_ok.err b/python/test-data-2.0/extras/testForwardRef5.out similarity index 100% rename from python/test-data-2.0/failing/testFunEq_ok.err rename to python/test-data-2.0/extras/testForwardRef5.out diff --git a/python/test-data-2.0/failing/testForwardRef5.py b/python/test-data-2.0/extras/testForwardRef5.py similarity index 100% rename from python/test-data-2.0/failing/testForwardRef5.py rename to python/test-data-2.0/extras/testForwardRef5.py diff --git a/python/test-data-2.0/failing/testGetSource.out b/python/test-data-2.0/extras/testFunEq_ok.err similarity index 100% rename from python/test-data-2.0/failing/testGetSource.out rename to python/test-data-2.0/extras/testFunEq_ok.err diff --git a/python/test-data-2.0/extras/testFunEq_ok.out b/python/test-data-2.0/extras/testFunEq_ok.out new file mode 100644 index 00000000..2887acb4 --- /dev/null +++ b/python/test-data-2.0/extras/testFunEq_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist +1 Test, 1 Fehler 🙁 diff --git a/python/test-data-2.0/failing/testFunEq_ok.py b/python/test-data-2.0/extras/testFunEq_ok.py similarity index 100% rename from python/test-data-2.0/failing/testFunEq_ok.py rename to python/test-data-2.0/extras/testFunEq_ok.py diff --git a/python/test-data-2.0/failing/testGetSource.err b/python/test-data-2.0/extras/testGetSource.err similarity index 81% rename from python/test-data-2.0/failing/testGetSource.err rename to python/test-data-2.0/extras/testGetSource.err index 9ff1685b..e5d1e1e0 100644 --- a/python/test-data-2.0/failing/testGetSource.err +++ b/python/test-data-2.0/extras/testGetSource.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testGetSource.py", line 11, in + File "test-data-2.0/extras/testGetSource.py", line 11, in Art = Literal('klein','mittag') # <= problem is here File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) @@ -10,7 +10,7 @@ WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` Wolltest du `Literal['klein', 'mittag']` schreiben? -## Datei test-data-2.0/failing/testGetSource.py +## Datei test-data-2.0/extras/testGetSource.py ## Typ deklariert in Zeile 11: Art = Literal('klein','mittag') # <= problem is here \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses1.out b/python/test-data-2.0/extras/testGetSource.out similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses1.out rename to python/test-data-2.0/extras/testGetSource.out diff --git a/python/test-data-2.0/failing/testGetSource.py b/python/test-data-2.0/extras/testGetSource.py similarity index 100% rename from python/test-data-2.0/failing/testGetSource.py rename to python/test-data-2.0/extras/testGetSource.py diff --git a/python/test-data-2.0/failing/testHintParentheses1.err b/python/test-data-2.0/extras/testHintParentheses1.err similarity index 62% rename from python/test-data-2.0/failing/testHintParentheses1.err rename to python/test-data-2.0/extras/testHintParentheses1.err index 1cd0aec0..3adea29c 100644 --- a/python/test-data-2.0/failing/testHintParentheses1.err +++ b/python/test-data-2.0/extras/testHintParentheses1.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testHintParentheses1.py", line 8, in + File "test-data-2.0/extras/testHintParentheses1.py", line 8, in check(foo([1,2,3]), 3) WyppTypeError: ungültiger Typ `list(int)` Wolltest du `list[int]` schreiben? -## Datei test-data-2.0/failing/testHintParentheses1.py +## Datei test-data-2.0/extras/testHintParentheses1.py ## Typ deklariert in Zeile 5: def foo(l: list(int)) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses2.out b/python/test-data-2.0/extras/testHintParentheses1.out similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses2.out rename to python/test-data-2.0/extras/testHintParentheses1.out diff --git a/python/test-data-2.0/failing/testHintParentheses1.py b/python/test-data-2.0/extras/testHintParentheses1.py similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses1.py rename to python/test-data-2.0/extras/testHintParentheses1.py diff --git a/python/test-data-2.0/failing/testHintParentheses2.err b/python/test-data-2.0/extras/testHintParentheses2.err similarity index 60% rename from python/test-data-2.0/failing/testHintParentheses2.err rename to python/test-data-2.0/extras/testHintParentheses2.err index f28b4d98..18944b1a 100644 --- a/python/test-data-2.0/failing/testHintParentheses2.err +++ b/python/test-data-2.0/extras/testHintParentheses2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testHintParentheses2.py", line 8, in + File "test-data-2.0/extras/testHintParentheses2.py", line 8, in foo(1, {}) WyppTypeError: ungültiger Typ `dict[1, list(int)]` -## Datei test-data-2.0/failing/testHintParentheses2.py +## Datei test-data-2.0/extras/testHintParentheses2.py ## Typ deklariert in Zeile 5: def foo(a: 'int', b: 'dict[1, list(int)]') -> int: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testHintParentheses3.out b/python/test-data-2.0/extras/testHintParentheses2.out similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses3.out rename to python/test-data-2.0/extras/testHintParentheses2.out diff --git a/python/test-data-2.0/failing/testHintParentheses2.py b/python/test-data-2.0/extras/testHintParentheses2.py similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses2.py rename to python/test-data-2.0/extras/testHintParentheses2.py diff --git a/python/test-data-2.0/failing/testHintParentheses3.err b/python/test-data-2.0/extras/testHintParentheses3.err similarity index 62% rename from python/test-data-2.0/failing/testHintParentheses3.err rename to python/test-data-2.0/extras/testHintParentheses3.err index 89f835a6..a7ab8ccf 100644 --- a/python/test-data-2.0/failing/testHintParentheses3.err +++ b/python/test-data-2.0/extras/testHintParentheses3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testHintParentheses3.py", line 9, in + File "test-data-2.0/extras/testHintParentheses3.py", line 9, in foo() WyppTypeError: ungültiger Typ `Union(list, str)` Wolltest du `Union[list, str]` schreiben? -## Datei test-data-2.0/failing/testHintParentheses3.py +## Datei test-data-2.0/extras/testHintParentheses3.py ## Typ deklariert in Zeile 6: def foo() -> Union(list, str): \ No newline at end of file diff --git a/python/test-data-2.0/failing/testImpossible.out b/python/test-data-2.0/extras/testHintParentheses3.out similarity index 100% rename from python/test-data-2.0/failing/testImpossible.out rename to python/test-data-2.0/extras/testHintParentheses3.out diff --git a/python/test-data-2.0/failing/testHintParentheses3.py b/python/test-data-2.0/extras/testHintParentheses3.py similarity index 100% rename from python/test-data-2.0/failing/testHintParentheses3.py rename to python/test-data-2.0/extras/testHintParentheses3.py diff --git a/python/test-data-2.0/failing/testImpossible.err b/python/test-data-2.0/extras/testImpossible.err similarity index 72% rename from python/test-data-2.0/failing/testImpossible.err rename to python/test-data-2.0/extras/testImpossible.err index f53d7602..43365f4a 100644 --- a/python/test-data-2.0/failing/testImpossible.err +++ b/python/test-data-2.0/extras/testImpossible.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testImpossible.py", line 3, in + File "test-data-2.0/extras/testImpossible.py", line 3, in impossible() File "site-lib/wypp/writeYourProgram.py", line 331, in impossible raise errors.ImpossibleError(msg) diff --git a/python/test-data-2.0/failing/testIndexError.out b/python/test-data-2.0/extras/testImpossible.out similarity index 100% rename from python/test-data-2.0/failing/testIndexError.out rename to python/test-data-2.0/extras/testImpossible.out diff --git a/python/test-data-2.0/failing/testImpossible.py b/python/test-data-2.0/extras/testImpossible.py similarity index 100% rename from python/test-data-2.0/failing/testImpossible.py rename to python/test-data-2.0/extras/testImpossible.py diff --git a/python/test-data-2.0/extras/testIndexError.err b/python/test-data-2.0/extras/testIndexError.err new file mode 100644 index 00000000..0afd5aa3 --- /dev/null +++ b/python/test-data-2.0/extras/testIndexError.err @@ -0,0 +1,6 @@ +Traceback (most recent call last): + File "test-data-2.0/extras/testIndexError.py", line 6, in + foo([1,2,3]) + File "test-data-2.0/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/test-data-2.0/failing/testInvalidLiteral.out b/python/test-data-2.0/extras/testIndexError.out similarity index 100% rename from python/test-data-2.0/failing/testInvalidLiteral.out rename to python/test-data-2.0/extras/testIndexError.out diff --git a/python/test-data-2.0/failing/testIndexError.py b/python/test-data-2.0/extras/testIndexError.py similarity index 100% rename from python/test-data-2.0/failing/testIndexError.py rename to python/test-data-2.0/extras/testIndexError.py diff --git a/python/test-data-2.0/failing/testInvalidLiteral.err b/python/test-data-2.0/extras/testInvalidLiteral.err similarity index 60% rename from python/test-data-2.0/failing/testInvalidLiteral.err rename to python/test-data-2.0/extras/testInvalidLiteral.err index f3b69339..62e84749 100644 --- a/python/test-data-2.0/failing/testInvalidLiteral.err +++ b/python/test-data-2.0/extras/testInvalidLiteral.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testInvalidLiteral.py", line 8, in + File "test-data-2.0/extras/testInvalidLiteral.py", line 8, in gameFull([['x']]) WyppTypeError: ungültiger Typ `list[list[['x', 'o', '-']]]` -## Datei test-data-2.0/failing/testInvalidLiteral.py +## Datei test-data-2.0/extras/testInvalidLiteral.py ## Typ deklariert in Zeile 5: def gameFull(game:Game)->bool: \ No newline at end of file diff --git a/python/test-data-2.0/failing/testIterable7_ok.err b/python/test-data-2.0/extras/testInvalidLiteral.out similarity index 100% rename from python/test-data-2.0/failing/testIterable7_ok.err rename to python/test-data-2.0/extras/testInvalidLiteral.out diff --git a/python/test-data-2.0/failing/testInvalidLiteral.py b/python/test-data-2.0/extras/testInvalidLiteral.py similarity index 100% rename from python/test-data-2.0/failing/testInvalidLiteral.py rename to python/test-data-2.0/extras/testInvalidLiteral.py diff --git a/python/test-data-2.0/failing/testLiteral1.out b/python/test-data-2.0/extras/testIterable7_ok.err similarity index 100% rename from python/test-data-2.0/failing/testLiteral1.out rename to python/test-data-2.0/extras/testIterable7_ok.err diff --git a/python/test-data-2.0/failing/testIterable7_ok.out b/python/test-data-2.0/extras/testIterable7_ok.out similarity index 100% rename from python/test-data-2.0/failing/testIterable7_ok.out rename to python/test-data-2.0/extras/testIterable7_ok.out diff --git a/python/test-data-2.0/failing/testIterable7_ok.py b/python/test-data-2.0/extras/testIterable7_ok.py similarity index 100% rename from python/test-data-2.0/failing/testIterable7_ok.py rename to python/test-data-2.0/extras/testIterable7_ok.py diff --git a/python/test-data-2.0/failing/testIterableImplicitAny.err b/python/test-data-2.0/extras/testIterableImplicitAny.err similarity index 72% rename from python/test-data-2.0/failing/testIterableImplicitAny.err rename to python/test-data-2.0/extras/testIterableImplicitAny.err index 34281df4..a405e4c2 100644 --- a/python/test-data-2.0/failing/testIterableImplicitAny.err +++ b/python/test-data-2.0/extras/testIterableImplicitAny.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testIterableImplicitAny.py", line 13, in + File "test-data-2.0/extras/testIterableImplicitAny.py", line 13, in foo(NotIterable()) WyppTypeError: NotIterable @@ -7,7 +7,7 @@ WyppTypeError: NotIterable Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Iterable` als erstes Argument. Aber der übergebene Wert hat Typ `NotIterable`. -## Datei test-data-2.0/failing/testIterableImplicitAny.py +## Datei test-data-2.0/extras/testIterableImplicitAny.py ## Fehlerhafter Aufruf in Zeile 13: foo(NotIterable()) diff --git a/python/test-data-2.0/failing/testIterableImplicitAny.out b/python/test-data-2.0/extras/testIterableImplicitAny.out similarity index 100% rename from python/test-data-2.0/failing/testIterableImplicitAny.out rename to python/test-data-2.0/extras/testIterableImplicitAny.out diff --git a/python/test-data-2.0/failing/testIterableImplicitAny.py b/python/test-data-2.0/extras/testIterableImplicitAny.py similarity index 100% rename from python/test-data-2.0/failing/testIterableImplicitAny.py rename to python/test-data-2.0/extras/testIterableImplicitAny.py diff --git a/python/test-data-2.0/failing/testLiteral1.err b/python/test-data-2.0/extras/testLiteral1.err similarity index 79% rename from python/test-data-2.0/failing/testLiteral1.err rename to python/test-data-2.0/extras/testLiteral1.err index 093a11d4..0185b9b7 100644 --- a/python/test-data-2.0/failing/testLiteral1.err +++ b/python/test-data-2.0/extras/testLiteral1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testLiteral1.py", line 3, in + File "test-data-2.0/extras/testLiteral1.py", line 3, in T = Literal('a', 'b') File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) @@ -10,7 +10,7 @@ WyppTypeError: ungültiger Typ `Literal('a', 'b')` Wolltest du `Literal['a', 'b']` schreiben? -## Datei test-data-2.0/failing/testLiteral1.py +## Datei test-data-2.0/extras/testLiteral1.py ## Typ deklariert in Zeile 3: T = Literal('a', 'b') \ No newline at end of file diff --git a/python/test-data-2.0/failing/testLockFactory.out b/python/test-data-2.0/extras/testLiteral1.out similarity index 100% rename from python/test-data-2.0/failing/testLockFactory.out rename to python/test-data-2.0/extras/testLiteral1.out diff --git a/python/test-data-2.0/failing/testLiteral1.py b/python/test-data-2.0/extras/testLiteral1.py similarity index 100% rename from python/test-data-2.0/failing/testLiteral1.py rename to python/test-data-2.0/extras/testLiteral1.py diff --git a/python/test-data-2.0/failing/testLockFactory.err b/python/test-data-2.0/extras/testLockFactory.err similarity index 74% rename from python/test-data-2.0/failing/testLockFactory.err rename to python/test-data-2.0/extras/testLockFactory.err index ec9f8552..039488f7 100644 --- a/python/test-data-2.0/failing/testLockFactory.err +++ b/python/test-data-2.0/extras/testLockFactory.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testLockFactory.py", line 15, in + File "test-data-2.0/extras/testLockFactory.py", line 15, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/testLockFactory.py +## Datei test-data-2.0/extras/testLockFactory.py ## Fehlerhafter Aufruf in Zeile 15: foo("not a lock") diff --git a/python/test-data-2.0/failing/testLockFactory2.out b/python/test-data-2.0/extras/testLockFactory.out similarity index 100% rename from python/test-data-2.0/failing/testLockFactory2.out rename to python/test-data-2.0/extras/testLockFactory.out diff --git a/python/test-data-2.0/failing/testLockFactory.py b/python/test-data-2.0/extras/testLockFactory.py similarity index 100% rename from python/test-data-2.0/failing/testLockFactory.py rename to python/test-data-2.0/extras/testLockFactory.py diff --git a/python/test-data-2.0/failing/testLockFactory2.err b/python/test-data-2.0/extras/testLockFactory2.err similarity index 73% rename from python/test-data-2.0/failing/testLockFactory2.err rename to python/test-data-2.0/extras/testLockFactory2.err index 64b4fb94..ac81c27e 100644 --- a/python/test-data-2.0/failing/testLockFactory2.err +++ b/python/test-data-2.0/extras/testLockFactory2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testLockFactory2.py", line 14, in + File "test-data-2.0/extras/testLockFactory2.py", line 14, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/testLockFactory2.py +## Datei test-data-2.0/extras/testLockFactory2.py ## Fehlerhafter Aufruf in Zeile 14: foo("not a lock") diff --git a/python/test-data-2.0/failing/testLockFactory_ok.err b/python/test-data-2.0/extras/testLockFactory2.out similarity index 100% rename from python/test-data-2.0/failing/testLockFactory_ok.err rename to python/test-data-2.0/extras/testLockFactory2.out diff --git a/python/test-data-2.0/failing/testLockFactory2.py b/python/test-data-2.0/extras/testLockFactory2.py similarity index 100% rename from python/test-data-2.0/failing/testLockFactory2.py rename to python/test-data-2.0/extras/testLockFactory2.py diff --git a/python/test-data-2.0/failing/testMissingReturn.out b/python/test-data-2.0/extras/testLockFactory_ok.err similarity index 100% rename from python/test-data-2.0/failing/testMissingReturn.out rename to python/test-data-2.0/extras/testLockFactory_ok.err diff --git a/python/test-data-2.0/failing/testLockFactory_ok.out b/python/test-data-2.0/extras/testLockFactory_ok.out similarity index 100% rename from python/test-data-2.0/failing/testLockFactory_ok.out rename to python/test-data-2.0/extras/testLockFactory_ok.out diff --git a/python/test-data-2.0/failing/testLockFactory_ok.py b/python/test-data-2.0/extras/testLockFactory_ok.py similarity index 100% rename from python/test-data-2.0/failing/testLockFactory_ok.py rename to python/test-data-2.0/extras/testLockFactory_ok.py diff --git a/python/test-data-2.0/failing/testMissingReturn.err b/python/test-data-2.0/extras/testMissingReturn.err similarity index 68% rename from python/test-data-2.0/failing/testMissingReturn.err rename to python/test-data-2.0/extras/testMissingReturn.err index 7fe943d7..4551c2b1 100644 --- a/python/test-data-2.0/failing/testMissingReturn.err +++ b/python/test-data-2.0/extras/testMissingReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testMissingReturn.py", line 6, in + File "test-data-2.0/extras/testMissingReturn.py", line 6, in print(billigStrom(500)) - File "test-data-2.0/failing/testMissingReturn.py", line 4, in billigStrom + File "test-data-2.0/extras/testMissingReturn.py", line 4, in billigStrom pass WyppTypeError: kein Rückgabewert vorhanden @@ -9,7 +9,7 @@ WyppTypeError: kein Rückgabewert vorhanden Rückgabewert vom Typ `float` erwartet bei Aufruf der Funktion `billigStrom`. Aber kein Rückgabewert vorhanden. -## Datei test-data-2.0/failing/testMissingReturn.py +## Datei test-data-2.0/extras/testMissingReturn.py ## Rückgabetyp deklariert in Zeile 3: def billigStrom(kwh: float) -> float: diff --git a/python/test-data-2.0/failing/testOriginalTypeNames_ok.err b/python/test-data-2.0/extras/testMissingReturn.out similarity index 100% rename from python/test-data-2.0/failing/testOriginalTypeNames_ok.err rename to python/test-data-2.0/extras/testMissingReturn.out diff --git a/python/test-data-2.0/failing/testMissingReturn.py b/python/test-data-2.0/extras/testMissingReturn.py similarity index 100% rename from python/test-data-2.0/failing/testMissingReturn.py rename to python/test-data-2.0/extras/testMissingReturn.py diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.out b/python/test-data-2.0/extras/testOriginalTypeNames_ok.err similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.out rename to python/test-data-2.0/extras/testOriginalTypeNames_ok.err diff --git a/python/test-data-2.0/failing/testOriginalTypeNames_ok.out b/python/test-data-2.0/extras/testOriginalTypeNames_ok.out similarity index 100% rename from python/test-data-2.0/failing/testOriginalTypeNames_ok.out rename to python/test-data-2.0/extras/testOriginalTypeNames_ok.out diff --git a/python/test-data-2.0/failing/testOriginalTypeNames_ok.py b/python/test-data-2.0/extras/testOriginalTypeNames_ok.py similarity index 100% rename from python/test-data-2.0/failing/testOriginalTypeNames_ok.py rename to python/test-data-2.0/extras/testOriginalTypeNames_ok.py diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.err rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.err diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.10 b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.10 rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.10 diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.11 b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.11 rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.11 diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 similarity index 60% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 index c6b5717d..30e21a6c 100644 --- a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.err-3.12 +++ b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testRecordSetTypeForwardRef.py", line 15, in + File "test-data-2.0/extras/testRecordSetTypeForwardRef.py", line 15, in m() - File "test-data-2.0/failing/testRecordSetTypeForwardRef.py", line 13, in m + File "test-data-2.0/extras/testRecordSetTypeForwardRef.py", line 13, in m r.x = "hello" WyppTypeError: "hello" @@ -9,7 +9,7 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `A`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei test-data-2.0/failing/testRecordSetTypeForwardRef.py +## Datei test-data-2.0/extras/testRecordSetTypeForwardRef.py ## Fehlerhafte Zuweisung in Zeile 13: r.x = "hello" diff --git a/python/test-data-2.0/failing/testRecordSetTypes.out b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.out similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypes.out rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.out diff --git a/python/test-data-2.0/failing/testRecordSetTypeForwardRef.py b/python/test-data-2.0/extras/testRecordSetTypeForwardRef.py similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypeForwardRef.py rename to python/test-data-2.0/extras/testRecordSetTypeForwardRef.py diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err b/python/test-data-2.0/extras/testRecordSetTypes.err similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypes.err rename to python/test-data-2.0/extras/testRecordSetTypes.err diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err-3.10 b/python/test-data-2.0/extras/testRecordSetTypes.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypes.err-3.10 rename to python/test-data-2.0/extras/testRecordSetTypes.err-3.10 diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err-3.11 b/python/test-data-2.0/extras/testRecordSetTypes.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypes.err-3.11 rename to python/test-data-2.0/extras/testRecordSetTypes.err-3.11 diff --git a/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 b/python/test-data-2.0/extras/testRecordSetTypes.err-3.12 similarity index 64% rename from python/test-data-2.0/failing/testRecordSetTypes.err-3.12 rename to python/test-data-2.0/extras/testRecordSetTypes.err-3.12 index 71bf8cee..a1dea348 100644 --- a/python/test-data-2.0/failing/testRecordSetTypes.err-3.12 +++ b/python/test-data-2.0/extras/testRecordSetTypes.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testRecordSetTypes.py", line 12, in + File "test-data-2.0/extras/testRecordSetTypes.py", line 12, in m() - File "test-data-2.0/failing/testRecordSetTypes.py", line 10, in m + File "test-data-2.0/extras/testRecordSetTypes.py", line 10, in m r.x = "hello" WyppTypeError: "hello" @@ -9,7 +9,7 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei test-data-2.0/failing/testRecordSetTypes.py +## Datei test-data-2.0/extras/testRecordSetTypes.py ## Fehlerhafte Zuweisung in Zeile 10: r.x = "hello" diff --git a/python/test-data-2.0/failing/testRecordTypes.out b/python/test-data-2.0/extras/testRecordSetTypes.out similarity index 100% rename from python/test-data-2.0/failing/testRecordTypes.out rename to python/test-data-2.0/extras/testRecordSetTypes.out diff --git a/python/test-data-2.0/failing/testRecordSetTypes.py b/python/test-data-2.0/extras/testRecordSetTypes.py similarity index 100% rename from python/test-data-2.0/failing/testRecordSetTypes.py rename to python/test-data-2.0/extras/testRecordSetTypes.py diff --git a/python/test-data-2.0/failing/testRecordTypes.err b/python/test-data-2.0/extras/testRecordTypes.err similarity index 100% rename from python/test-data-2.0/failing/testRecordTypes.err rename to python/test-data-2.0/extras/testRecordTypes.err diff --git a/python/test-data-2.0/failing/testRecordTypes.err-3.12 b/python/test-data-2.0/extras/testRecordTypes.err-3.12 similarity index 74% rename from python/test-data-2.0/failing/testRecordTypes.err-3.12 rename to python/test-data-2.0/extras/testRecordTypes.err-3.12 index d296bf88..7e9f2b03 100644 --- a/python/test-data-2.0/failing/testRecordTypes.err-3.12 +++ b/python/test-data-2.0/extras/testRecordTypes.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testRecordTypes.py", line 8, in + File "test-data-2.0/extras/testRecordTypes.py", line 8, in p = Point(1, '5') WyppTypeError: '5' @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/testRecordTypes.py +## Datei test-data-2.0/extras/testRecordTypes.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(1, '5') diff --git a/python/test-data-2.0/failing/testTodo.out b/python/test-data-2.0/extras/testRecordTypes.out similarity index 100% rename from python/test-data-2.0/failing/testTodo.out rename to python/test-data-2.0/extras/testRecordTypes.out diff --git a/python/test-data-2.0/failing/testRecordTypes.py b/python/test-data-2.0/extras/testRecordTypes.py similarity index 100% rename from python/test-data-2.0/failing/testRecordTypes.py rename to python/test-data-2.0/extras/testRecordTypes.py diff --git a/python/test-data-2.0/failing/testTodo.err b/python/test-data-2.0/extras/testTodo.err similarity index 69% rename from python/test-data-2.0/failing/testTodo.err rename to python/test-data-2.0/extras/testTodo.err index df03d1db..17d4c7d0 100644 --- a/python/test-data-2.0/failing/testTodo.err +++ b/python/test-data-2.0/extras/testTodo.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTodo.py", line 3, in + File "test-data-2.0/extras/testTodo.py", line 3, in todo() File "site-lib/wypp/writeYourProgram.py", line 326, in todo raise errors.TodoError(msg) diff --git a/python/test-data-2.0/failing/testTraceback2.out b/python/test-data-2.0/extras/testTodo.out similarity index 100% rename from python/test-data-2.0/failing/testTraceback2.out rename to python/test-data-2.0/extras/testTodo.out diff --git a/python/test-data-2.0/failing/testTodo.py b/python/test-data-2.0/extras/testTodo.py similarity index 100% rename from python/test-data-2.0/failing/testTodo.py rename to python/test-data-2.0/extras/testTodo.py diff --git a/python/test-data-2.0/extras/testTraceback.err b/python/test-data-2.0/extras/testTraceback.err new file mode 100644 index 00000000..d9c2c210 --- /dev/null +++ b/python/test-data-2.0/extras/testTraceback.err @@ -0,0 +1,6 @@ +Traceback (most recent call last): + File "test-data-2.0/extras/testTraceback.py", line 9, in + foo(lst) + File "test-data-2.0/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-2.0/failing/testTraceback.out b/python/test-data-2.0/extras/testTraceback.out similarity index 100% rename from python/test-data-2.0/failing/testTraceback.out rename to python/test-data-2.0/extras/testTraceback.out diff --git a/python/test-data-2.0/failing/testTraceback.py b/python/test-data-2.0/extras/testTraceback.py similarity index 100% rename from python/test-data-2.0/failing/testTraceback.py rename to python/test-data-2.0/extras/testTraceback.py diff --git a/python/test-data-2.0/failing/testTraceback2.err b/python/test-data-2.0/extras/testTraceback2.err similarity index 74% rename from python/test-data-2.0/failing/testTraceback2.err rename to python/test-data-2.0/extras/testTraceback2.err index cb7b5f82..3102b5ed 100644 --- a/python/test-data-2.0/failing/testTraceback2.err +++ b/python/test-data-2.0/extras/testTraceback2.err @@ -1,4 +1,4 @@ -File "/Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testTraceback2.py", line 3 +File "/Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testTraceback2.py", line 3 lst = [1,2,3 ^ SyntaxError: '[' was never closed \ No newline at end of file diff --git a/python/test-data-2.0/failing/testTraceback2.err-3.10.0 b/python/test-data-2.0/extras/testTraceback2.err-3.10.0 similarity index 100% rename from python/test-data-2.0/failing/testTraceback2.err-3.10.0 rename to python/test-data-2.0/extras/testTraceback2.err-3.10.0 diff --git a/python/test-data-2.0/failing/testTraceback2.err-3.9 b/python/test-data-2.0/extras/testTraceback2.err-3.9 similarity index 100% rename from python/test-data-2.0/failing/testTraceback2.err-3.9 rename to python/test-data-2.0/extras/testTraceback2.err-3.9 diff --git a/python/test-data-2.0/failing/testTraceback3.out b/python/test-data-2.0/extras/testTraceback2.out similarity index 100% rename from python/test-data-2.0/failing/testTraceback3.out rename to python/test-data-2.0/extras/testTraceback2.out diff --git a/python/test-data-2.0/failing/testTraceback2.py b/python/test-data-2.0/extras/testTraceback2.py similarity index 100% rename from python/test-data-2.0/failing/testTraceback2.py rename to python/test-data-2.0/extras/testTraceback2.py diff --git a/python/test-data-2.0/failing/testTraceback3.err b/python/test-data-2.0/extras/testTraceback3.err similarity index 57% rename from python/test-data-2.0/failing/testTraceback3.err rename to python/test-data-2.0/extras/testTraceback3.err index bcf0ef4c..4170a7a7 100644 --- a/python/test-data-2.0/failing/testTraceback3.err +++ b/python/test-data-2.0/extras/testTraceback3.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTraceback3.py", line 2, in + File "test-data-2.0/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/test-data-2.0/failing/testTypes1.out b/python/test-data-2.0/extras/testTraceback3.out similarity index 100% rename from python/test-data-2.0/failing/testTypes1.out rename to python/test-data-2.0/extras/testTraceback3.out diff --git a/python/test-data-2.0/failing/testTraceback3.py b/python/test-data-2.0/extras/testTraceback3.py similarity index 100% rename from python/test-data-2.0/failing/testTraceback3.py rename to python/test-data-2.0/extras/testTraceback3.py diff --git a/python/test-data-2.0/failing/testTypes1.err b/python/test-data-2.0/extras/testTypes1.err similarity index 73% rename from python/test-data-2.0/failing/testTypes1.err rename to python/test-data-2.0/extras/testTypes1.err index 2baa29b8..d4590d30 100644 --- a/python/test-data-2.0/failing/testTypes1.err +++ b/python/test-data-2.0/extras/testTypes1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypes1.py", line 4, in + File "test-data-2.0/extras/testTypes1.py", line 4, in inc("1") WyppTypeError: "1" @@ -7,7 +7,7 @@ WyppTypeError: "1" Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/failing/testTypes1.py +## Datei test-data-2.0/extras/testTypes1.py ## Fehlerhafter Aufruf in Zeile 4: inc("1") diff --git a/python/test-data-2.0/failing/testTypes1.err-notypes b/python/test-data-2.0/extras/testTypes1.err-notypes similarity index 100% rename from python/test-data-2.0/failing/testTypes1.err-notypes rename to python/test-data-2.0/extras/testTypes1.err-notypes diff --git a/python/test-data-2.0/failing/testTypesDict1.out b/python/test-data-2.0/extras/testTypes1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesDict1.out rename to python/test-data-2.0/extras/testTypes1.out diff --git a/python/test-data-2.0/failing/testTypes1.py b/python/test-data-2.0/extras/testTypes1.py similarity index 100% rename from python/test-data-2.0/failing/testTypes1.py rename to python/test-data-2.0/extras/testTypes1.py diff --git a/python/test-data-2.0/failing/testTypesDict1.err b/python/test-data-2.0/extras/testTypesDict1.err similarity index 100% rename from python/test-data-2.0/failing/testTypesDict1.err rename to python/test-data-2.0/extras/testTypesDict1.err diff --git a/python/test-data-2.0/failing/testTypesDict3.out b/python/test-data-2.0/extras/testTypesDict1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesDict3.out rename to python/test-data-2.0/extras/testTypesDict1.out diff --git a/python/test-data-2.0/failing/testTypesDict3.err b/python/test-data-2.0/extras/testTypesDict3.err similarity index 69% rename from python/test-data-2.0/failing/testTypesDict3.err rename to python/test-data-2.0/extras/testTypesDict3.err index 91e44513..d0058fe0 100644 --- a/python/test-data-2.0/failing/testTypesDict3.err +++ b/python/test-data-2.0/extras/testTypesDict3.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesDict3.py", line 11, in + File "test-data-2.0/extras/testTypesDict3.py", line 11, in foo({'y': func}) - File "test-data-2.0/failing/testTypesDict3.py", line 8, in foo + File "test-data-2.0/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 test-data-2.0/failing/testTypesDict3.py +## Datei test-data-2.0/extras/testTypesDict3.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: diff --git a/python/test-data-2.0/failing/testTypesDict4.out b/python/test-data-2.0/extras/testTypesDict3.out similarity index 100% rename from python/test-data-2.0/failing/testTypesDict4.out rename to python/test-data-2.0/extras/testTypesDict3.out diff --git a/python/test-data-2.0/failing/testTypesDict3.py b/python/test-data-2.0/extras/testTypesDict3.py similarity index 100% rename from python/test-data-2.0/failing/testTypesDict3.py rename to python/test-data-2.0/extras/testTypesDict3.py diff --git a/python/test-data-2.0/failing/testTypesDict4.err b/python/test-data-2.0/extras/testTypesDict4.err similarity index 64% rename from python/test-data-2.0/failing/testTypesDict4.err rename to python/test-data-2.0/extras/testTypesDict4.err index a5b3aa36..70aec66b 100644 --- a/python/test-data-2.0/failing/testTypesDict4.err +++ b/python/test-data-2.0/extras/testTypesDict4.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesDict4.py", line 14, in + File "test-data-2.0/extras/testTypesDict4.py", line 14, in bar({'y': func}) # error - File "test-data-2.0/failing/testTypesDict4.py", line 11, in bar + File "test-data-2.0/extras/testTypesDict4.py", line 11, in bar return foo(d) - File "test-data-2.0/failing/testTypesDict4.py", line 8, in foo + File "test-data-2.0/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 test-data-2.0/failing/testTypesDict4.py +## Datei test-data-2.0/extras/testTypesDict4.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.out b/python/test-data-2.0/extras/testTypesDict4.out similarity index 100% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns3.out rename to python/test-data-2.0/extras/testTypesDict4.out diff --git a/python/test-data-2.0/failing/testTypesDict4.py b/python/test-data-2.0/extras/testTypesDict4.py similarity index 100% rename from python/test-data-2.0/failing/testTypesDict4.py rename to python/test-data-2.0/extras/testTypesDict4.py diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns.err b/python/test-data-2.0/extras/testTypesHigherOrderFuns.err similarity index 68% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns.err rename to python/test-data-2.0/extras/testTypesHigherOrderFuns.err index 8e6321ed..6e781461 100644 --- a/python/test-data-2.0/failing/testTypesHigherOrderFuns.err +++ b/python/test-data-2.0/extras/testTypesHigherOrderFuns.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesHigherOrderFuns.py", line 10, in + File "test-data-2.0/extras/testTypesHigherOrderFuns.py", line 10, in map(["hello", "1"], lambda x: x) - File "test-data-2.0/failing/testTypesHigherOrderFuns.py", line 7, in map + File "test-data-2.0/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 test-data-2.0/failing/testTypesHigherOrderFuns.py +## Datei test-data-2.0/extras/testTypesHigherOrderFuns.py ## Rückgabetyp deklariert in Zeile 3: def map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]: diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns.out similarity index 100% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns.out diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns.py b/python/test-data-2.0/extras/testTypesHigherOrderFuns.py similarity index 100% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns.py rename to python/test-data-2.0/extras/testTypesHigherOrderFuns.py diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err b/python/test-data-2.0/extras/testTypesHigherOrderFuns3.err similarity index 78% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns3.err rename to python/test-data-2.0/extras/testTypesHigherOrderFuns3.err index f314e0eb..7c818faf 100644 --- a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.err +++ b/python/test-data-2.0/extras/testTypesHigherOrderFuns3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesHigherOrderFuns3.py", line 38, in + File "test-data-2.0/extras/testTypesHigherOrderFuns3.py", line 38, in homePoints: Callable[[GameResult], int] = mkGamePoints(42) WyppTypeError: 42 @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/testTypesHigherOrderFuns3.py +## Datei test-data-2.0/extras/testTypesHigherOrderFuns3.py ## Fehlerhafter Aufruf in Zeile 38: homePoints: Callable[[GameResult], int] = mkGamePoints(42) diff --git a/python/test-data-2.0/failing/testTypesProtos1.out b/python/test-data-2.0/extras/testTypesHigherOrderFuns3.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos1.out rename to python/test-data-2.0/extras/testTypesHigherOrderFuns3.out diff --git a/python/test-data-2.0/failing/testTypesHigherOrderFuns3.py b/python/test-data-2.0/extras/testTypesHigherOrderFuns3.py similarity index 100% rename from python/test-data-2.0/failing/testTypesHigherOrderFuns3.py rename to python/test-data-2.0/extras/testTypesHigherOrderFuns3.py diff --git a/python/test-data-2.0/failing/testTypesProtos1.err b/python/test-data-2.0/extras/testTypesProtos1.err similarity index 68% rename from python/test-data-2.0/failing/testTypesProtos1.err rename to python/test-data-2.0/extras/testTypesProtos1.err index 55d08a22..2b373448 100644 --- a/python/test-data-2.0/failing/testTypesProtos1.err +++ b/python/test-data-2.0/extras/testTypesProtos1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos1.py", line 21, in + File "test-data-2.0/extras/testTypesProtos1.py", line 21, in doSomething(Dog()) - File "test-data-2.0/failing/testTypesProtos1.py", line 19, in doSomething + File "test-data-2.0/extras/testTypesProtos1.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesProtos1.py +## Datei test-data-2.0/extras/testTypesProtos1.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/failing/testTypesProtos2.out b/python/test-data-2.0/extras/testTypesProtos1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos2.out rename to python/test-data-2.0/extras/testTypesProtos1.out diff --git a/python/test-data-2.0/failing/testTypesProtos1.py b/python/test-data-2.0/extras/testTypesProtos1.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos1.py rename to python/test-data-2.0/extras/testTypesProtos1.py diff --git a/python/test-data-2.0/failing/testTypesProtos2.err b/python/test-data-2.0/extras/testTypesProtos2.err similarity index 68% rename from python/test-data-2.0/failing/testTypesProtos2.err rename to python/test-data-2.0/extras/testTypesProtos2.err index c8a498a8..ecf6e504 100644 --- a/python/test-data-2.0/failing/testTypesProtos2.err +++ b/python/test-data-2.0/extras/testTypesProtos2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos2.py", line 21, in + File "test-data-2.0/extras/testTypesProtos2.py", line 21, in doSomething(Dog()) - File "test-data-2.0/failing/testTypesProtos2.py", line 19, in doSomething + File "test-data-2.0/extras/testTypesProtos2.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesProtos2.py +## Datei test-data-2.0/extras/testTypesProtos2.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/failing/testTypesProtos3.out b/python/test-data-2.0/extras/testTypesProtos2.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos3.out rename to python/test-data-2.0/extras/testTypesProtos2.out diff --git a/python/test-data-2.0/failing/testTypesProtos2.py b/python/test-data-2.0/extras/testTypesProtos2.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos2.py rename to python/test-data-2.0/extras/testTypesProtos2.py diff --git a/python/test-data-2.0/failing/testTypesProtos3.err b/python/test-data-2.0/extras/testTypesProtos3.err similarity index 69% rename from python/test-data-2.0/failing/testTypesProtos3.err rename to python/test-data-2.0/extras/testTypesProtos3.err index a746c055..adde3200 100644 --- a/python/test-data-2.0/failing/testTypesProtos3.err +++ b/python/test-data-2.0/extras/testTypesProtos3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos3.py", line 21, in + File "test-data-2.0/extras/testTypesProtos3.py", line 21, in doSomething(Dog()) - File "test-data-2.0/failing/testTypesProtos3.py", line 19, in doSomething + File "test-data-2.0/extras/testTypesProtos3.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesProtos3.py +## Datei test-data-2.0/extras/testTypesProtos3.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/failing/testTypesProtos6.out b/python/test-data-2.0/extras/testTypesProtos3.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos6.out rename to python/test-data-2.0/extras/testTypesProtos3.out diff --git a/python/test-data-2.0/failing/testTypesProtos3.py b/python/test-data-2.0/extras/testTypesProtos3.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos3.py rename to python/test-data-2.0/extras/testTypesProtos3.py diff --git a/python/test-data-2.0/failing/testTypesProtos4.err b/python/test-data-2.0/extras/testTypesProtos4.err similarity index 65% rename from python/test-data-2.0/failing/testTypesProtos4.err rename to python/test-data-2.0/extras/testTypesProtos4.err index ad0b8b1e..d5d98d93 100644 --- a/python/test-data-2.0/failing/testTypesProtos4.err +++ b/python/test-data-2.0/extras/testTypesProtos4.err @@ -1,9 +1,9 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos4.py", line 27, in + File "test-data-2.0/extras/testTypesProtos4.py", line 27, in print(foo(ConcreteWrong())) - File "test-data-2.0/failing/testTypesProtos4.py", line 24, in foo + File "test-data-2.0/extras/testTypesProtos4.py", line 24, in foo return fn(2) - File "test-data-2.0/failing/testTypesProtos4.py", line 20, in + File "test-data-2.0/extras/testTypesProtos4.py", line 20, in return lambda x: bar(x) # invalid call of bar with argument of type int WyppTypeError: 2 @@ -11,7 +11,7 @@ WyppTypeError: 2 Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/failing/testTypesProtos4.py +## Datei test-data-2.0/extras/testTypesProtos4.py ## Fehlerhafter Aufruf in Zeile 20: return lambda x: bar(x) # invalid call of bar with argument of type int diff --git a/python/test-data-2.0/failing/testTypesProtos4.out b/python/test-data-2.0/extras/testTypesProtos4.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos4.out rename to python/test-data-2.0/extras/testTypesProtos4.out diff --git a/python/test-data-2.0/failing/testTypesProtos4.py b/python/test-data-2.0/extras/testTypesProtos4.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos4.py rename to python/test-data-2.0/extras/testTypesProtos4.py diff --git a/python/test-data-2.0/failing/testTypesProtos6.err b/python/test-data-2.0/extras/testTypesProtos6.err similarity index 56% rename from python/test-data-2.0/failing/testTypesProtos6.err rename to python/test-data-2.0/extras/testTypesProtos6.err index ed839f30..5cccd08e 100644 --- a/python/test-data-2.0/failing/testTypesProtos6.err +++ b/python/test-data-2.0/extras/testTypesProtos6.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos6.py", line 57, in + File "test-data-2.0/extras/testTypesProtos6.py", line 57, in print(computeTotalSize(root)) - File "test-data-2.0/failing/testTypesProtos6.py", line 50, in computeTotalSize + File "test-data-2.0/extras/testTypesProtos6.py", line 50, in computeTotalSize fs.accept(visitor) - File "test-data-2.0/failing/testTypesProtos6.py", line 19, in accept + File "test-data-2.0/extras/testTypesProtos6.py", line 19, in accept visitor.visitDirectory(self) - File "test-data-2.0/failing/testTypesProtos6.py", line 41, in visitDirectory + File "test-data-2.0/extras/testTypesProtos6.py", line 41, in visitDirectory c.accept(self) - File "test-data-2.0/failing/testTypesProtos6.py", line 28, in accept + File "test-data-2.0/extras/testTypesProtos6.py", line 28, in accept visitor.visitFile(self) WyppTypeError: <__wypp__.File object at 0x00> @@ -15,7 +15,7 @@ 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 test-data-2.0/failing/testTypesProtos6.py +## Datei test-data-2.0/extras/testTypesProtos6.py ## Fehlerhafter Aufruf in Zeile 28: visitor.visitFile(self) diff --git a/python/test-data-2.0/failing/testTypesProtos7.out b/python/test-data-2.0/extras/testTypesProtos6.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos7.out rename to python/test-data-2.0/extras/testTypesProtos6.out diff --git a/python/test-data-2.0/failing/testTypesProtos6.py b/python/test-data-2.0/extras/testTypesProtos6.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos6.py rename to python/test-data-2.0/extras/testTypesProtos6.py diff --git a/python/test-data-2.0/failing/testTypesProtos7.err b/python/test-data-2.0/extras/testTypesProtos7.err similarity index 55% rename from python/test-data-2.0/failing/testTypesProtos7.err rename to python/test-data-2.0/extras/testTypesProtos7.err index 927c7fb1..1a10c6e2 100644 --- a/python/test-data-2.0/failing/testTypesProtos7.err +++ b/python/test-data-2.0/extras/testTypesProtos7.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos7.py", line 76, in + File "test-data-2.0/extras/testTypesProtos7.py", line 76, in print(computeTotalSize(root)) - File "test-data-2.0/failing/testTypesProtos7.py", line 69, in computeTotalSize + File "test-data-2.0/extras/testTypesProtos7.py", line 69, in computeTotalSize fs.accept(visitor) - File "test-data-2.0/failing/testTypesProtos7.py", line 36, in accept + File "test-data-2.0/extras/testTypesProtos7.py", line 36, in accept visitor.visitDirectory(self) - File "test-data-2.0/failing/testTypesProtos7.py", line 60, in visitDirectory + File "test-data-2.0/extras/testTypesProtos7.py", line 60, in visitDirectory c.accept(self) - File "test-data-2.0/failing/testTypesProtos7.py", line 47, in accept + File "test-data-2.0/extras/testTypesProtos7.py", line 47, in accept visitor.visitFile(self) WyppTypeError: File('notes.txt') @@ -15,7 +15,7 @@ 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 test-data-2.0/failing/testTypesProtos7.py +## Datei test-data-2.0/extras/testTypesProtos7.py ## Fehlerhafter Aufruf in Zeile 47: visitor.visitFile(self) diff --git a/python/test-data-2.0/failing/testTypesProtos8.out b/python/test-data-2.0/extras/testTypesProtos7.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos8.out rename to python/test-data-2.0/extras/testTypesProtos7.out diff --git a/python/test-data-2.0/failing/testTypesProtos7.py b/python/test-data-2.0/extras/testTypesProtos7.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos7.py rename to python/test-data-2.0/extras/testTypesProtos7.py diff --git a/python/test-data-2.0/failing/testTypesProtos8.err b/python/test-data-2.0/extras/testTypesProtos8.err similarity index 67% rename from python/test-data-2.0/failing/testTypesProtos8.err rename to python/test-data-2.0/extras/testTypesProtos8.err index e5656903..e7b35965 100644 --- a/python/test-data-2.0/failing/testTypesProtos8.err +++ b/python/test-data-2.0/extras/testTypesProtos8.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos8.py", line 12, in + File "test-data-2.0/extras/testTypesProtos8.py", line 12, in bar(Sub()) - File "test-data-2.0/failing/testTypesProtos8.py", line 10, in bar + File "test-data-2.0/extras/testTypesProtos8.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesProtos8.py +## Datei test-data-2.0/extras/testTypesProtos8.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") diff --git a/python/test-data-2.0/failing/testTypesProtos9.out b/python/test-data-2.0/extras/testTypesProtos8.out similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos9.out rename to python/test-data-2.0/extras/testTypesProtos8.out diff --git a/python/test-data-2.0/failing/testTypesProtos8.py b/python/test-data-2.0/extras/testTypesProtos8.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos8.py rename to python/test-data-2.0/extras/testTypesProtos8.py diff --git a/python/test-data-2.0/failing/testTypesProtos9.err b/python/test-data-2.0/extras/testTypesProtos9.err similarity index 67% rename from python/test-data-2.0/failing/testTypesProtos9.err rename to python/test-data-2.0/extras/testTypesProtos9.err index 712591a4..f2ff546e 100644 --- a/python/test-data-2.0/failing/testTypesProtos9.err +++ b/python/test-data-2.0/extras/testTypesProtos9.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesProtos9.py", line 12, in + File "test-data-2.0/extras/testTypesProtos9.py", line 12, in bar(Sub()) - File "test-data-2.0/failing/testTypesProtos9.py", line 10, in bar + File "test-data-2.0/extras/testTypesProtos9.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesProtos9.py +## Datei test-data-2.0/extras/testTypesProtos9.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") diff --git a/python/test-data-2.0/failing/testTypesSet1.out b/python/test-data-2.0/extras/testTypesProtos9.out similarity index 100% rename from python/test-data-2.0/failing/testTypesSet1.out rename to python/test-data-2.0/extras/testTypesProtos9.out diff --git a/python/test-data-2.0/failing/testTypesProtos9.py b/python/test-data-2.0/extras/testTypesProtos9.py similarity index 100% rename from python/test-data-2.0/failing/testTypesProtos9.py rename to python/test-data-2.0/extras/testTypesProtos9.py diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err b/python/test-data-2.0/extras/testTypesRecordInheritance.err similarity index 100% rename from python/test-data-2.0/failing/testTypesRecordInheritance.err rename to python/test-data-2.0/extras/testTypesRecordInheritance.err diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.10 b/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testTypesRecordInheritance.err-3.10 rename to python/test-data-2.0/extras/testTypesRecordInheritance.err-3.10 diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.11 b/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testTypesRecordInheritance.err-3.11 rename to python/test-data-2.0/extras/testTypesRecordInheritance.err-3.11 diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 b/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 similarity index 70% rename from python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 rename to python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 index 41306715..554179b8 100644 --- a/python/test-data-2.0/failing/testTypesRecordInheritance.err-3.12 +++ b/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesRecordInheritance.py", line 18, in + File "test-data-2.0/extras/testTypesRecordInheritance.py", line 18, in Point3D(1,2, "foo") WyppTypeError: "foo" @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/testTypesRecordInheritance.py +## Datei test-data-2.0/extras/testTypesRecordInheritance.py ## Fehlerhafter Aufruf in Zeile 18: Point3D(1,2, "foo") diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.out b/python/test-data-2.0/extras/testTypesRecordInheritance.out similarity index 100% rename from python/test-data-2.0/failing/testTypesRecordInheritance.out rename to python/test-data-2.0/extras/testTypesRecordInheritance.out diff --git a/python/test-data-2.0/failing/testTypesRecordInheritance.py b/python/test-data-2.0/extras/testTypesRecordInheritance.py similarity index 100% rename from python/test-data-2.0/failing/testTypesRecordInheritance.py rename to python/test-data-2.0/extras/testTypesRecordInheritance.py diff --git a/python/test-data-2.0/failing/testTypesReturn.err b/python/test-data-2.0/extras/testTypesReturn.err similarity index 70% rename from python/test-data-2.0/failing/testTypesReturn.err rename to python/test-data-2.0/extras/testTypesReturn.err index 6dfe3a66..1f3d18ba 100644 --- a/python/test-data-2.0/failing/testTypesReturn.err +++ b/python/test-data-2.0/extras/testTypesReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesReturn.py", line 8, in + File "test-data-2.0/extras/testTypesReturn.py", line 8, in foo(False) - File "test-data-2.0/failing/testTypesReturn.py", line 6, in foo + File "test-data-2.0/extras/testTypesReturn.py", line 6, in foo return 'you stupid' WyppTypeError: 'you stupid' @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesReturn.py +## Datei test-data-2.0/extras/testTypesReturn.py ## Rückgabetyp deklariert in Zeile 1: def foo(flag: bool) -> int: diff --git a/python/test-data-2.0/failing/testTypesReturn.out b/python/test-data-2.0/extras/testTypesReturn.out similarity index 100% rename from python/test-data-2.0/failing/testTypesReturn.out rename to python/test-data-2.0/extras/testTypesReturn.out diff --git a/python/test-data-2.0/failing/testTypesReturn.py b/python/test-data-2.0/extras/testTypesReturn.py similarity index 100% rename from python/test-data-2.0/failing/testTypesReturn.py rename to python/test-data-2.0/extras/testTypesReturn.py diff --git a/python/test-data-2.0/failing/testTypesSequence1.err b/python/test-data-2.0/extras/testTypesSequence1.err similarity index 72% rename from python/test-data-2.0/failing/testTypesSequence1.err rename to python/test-data-2.0/extras/testTypesSequence1.err index ea547219..215bdd12 100644 --- a/python/test-data-2.0/failing/testTypesSequence1.err +++ b/python/test-data-2.0/extras/testTypesSequence1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesSequence1.py", line 10, in + File "test-data-2.0/extras/testTypesSequence1.py", line 10, in foo(1) # should fail WyppTypeError: 1 @@ -7,7 +7,7 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/failing/testTypesSequence1.py +## Datei test-data-2.0/extras/testTypesSequence1.py ## Fehlerhafter Aufruf in Zeile 10: foo(1) # should fail diff --git a/python/test-data-2.0/failing/testTypesSequence1.out b/python/test-data-2.0/extras/testTypesSequence1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesSequence1.out rename to python/test-data-2.0/extras/testTypesSequence1.out diff --git a/python/test-data-2.0/failing/testTypesSequence1.py b/python/test-data-2.0/extras/testTypesSequence1.py similarity index 100% rename from python/test-data-2.0/failing/testTypesSequence1.py rename to python/test-data-2.0/extras/testTypesSequence1.py diff --git a/python/test-data-2.0/failing/testTypesSequence2.err b/python/test-data-2.0/extras/testTypesSequence2.err similarity index 74% rename from python/test-data-2.0/failing/testTypesSequence2.err rename to python/test-data-2.0/extras/testTypesSequence2.err index 62d8517b..820c87f6 100644 --- a/python/test-data-2.0/failing/testTypesSequence2.err +++ b/python/test-data-2.0/extras/testTypesSequence2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesSequence2.py", line 11, in + File "test-data-2.0/extras/testTypesSequence2.py", line 11, in foo("Hello!") # should fail WyppTypeError: "Hello!" @@ -7,7 +7,7 @@ WyppTypeError: "Hello!" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence[int]` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/failing/testTypesSequence2.py +## Datei test-data-2.0/extras/testTypesSequence2.py ## Fehlerhafter Aufruf in Zeile 11: foo("Hello!") # should fail diff --git a/python/test-data-2.0/failing/testTypesSequence2.out b/python/test-data-2.0/extras/testTypesSequence2.out similarity index 100% rename from python/test-data-2.0/failing/testTypesSequence2.out rename to python/test-data-2.0/extras/testTypesSequence2.out diff --git a/python/test-data-2.0/failing/testTypesSequence2.py b/python/test-data-2.0/extras/testTypesSequence2.py similarity index 100% rename from python/test-data-2.0/failing/testTypesSequence2.py rename to python/test-data-2.0/extras/testTypesSequence2.py diff --git a/python/test-data-2.0/failing/testTypesSet1.err b/python/test-data-2.0/extras/testTypesSet1.err similarity index 100% rename from python/test-data-2.0/failing/testTypesSet1.err rename to python/test-data-2.0/extras/testTypesSet1.err diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.out b/python/test-data-2.0/extras/testTypesSet1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesSubclassing1.out rename to python/test-data-2.0/extras/testTypesSet1.out diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.err b/python/test-data-2.0/extras/testTypesSubclassing1.err similarity index 68% rename from python/test-data-2.0/failing/testTypesSubclassing1.err rename to python/test-data-2.0/extras/testTypesSubclassing1.err index 253b0520..20112e32 100644 --- a/python/test-data-2.0/failing/testTypesSubclassing1.err +++ b/python/test-data-2.0/extras/testTypesSubclassing1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesSubclassing1.py", line 29, in + File "test-data-2.0/extras/testTypesSubclassing1.py", line 29, in feedAnimal(dog) - File "test-data-2.0/failing/testTypesSubclassing1.py", line 26, in feedAnimal + File "test-data-2.0/extras/testTypesSubclassing1.py", line 26, in feedAnimal a.feed(AnimalFood('some cat food')) WyppTypeError: @@ -9,7 +9,7 @@ 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 test-data-2.0/failing/testTypesSubclassing1.py +## Datei test-data-2.0/extras/testTypesSubclassing1.py ## Fehlerhafter Aufruf in Zeile 26: a.feed(AnimalFood('some cat food')) diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.out b/python/test-data-2.0/extras/testTypesSubclassing1.out similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg.out rename to python/test-data-2.0/extras/testTypesSubclassing1.out diff --git a/python/test-data-2.0/failing/testTypesSubclassing1.py b/python/test-data-2.0/extras/testTypesSubclassing1.py similarity index 100% rename from python/test-data-2.0/failing/testTypesSubclassing1.py rename to python/test-data-2.0/extras/testTypesSubclassing1.py diff --git a/python/test-data-2.0/failing/testTypesTuple1.err b/python/test-data-2.0/extras/testTypesTuple1.err similarity index 72% rename from python/test-data-2.0/failing/testTypesTuple1.err rename to python/test-data-2.0/extras/testTypesTuple1.err index 9e3c2041..75fab25a 100644 --- a/python/test-data-2.0/failing/testTypesTuple1.err +++ b/python/test-data-2.0/extras/testTypesTuple1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testTypesTuple1.py", line 5, in + File "test-data-2.0/extras/testTypesTuple1.py", line 5, in foo(1) WyppTypeError: 1 @@ -7,7 +7,7 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `tuple[int, ...]` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/failing/testTypesTuple1.py +## Datei test-data-2.0/extras/testTypesTuple1.py ## Fehlerhafter Aufruf in Zeile 5: foo(1) diff --git a/python/test-data-2.0/failing/testTypesTuple1.out b/python/test-data-2.0/extras/testTypesTuple1.out similarity index 100% rename from python/test-data-2.0/failing/testTypesTuple1.out rename to python/test-data-2.0/extras/testTypesTuple1.out diff --git a/python/test-data-2.0/failing/testTypesTuple1.py b/python/test-data-2.0/extras/testTypesTuple1.py similarity index 100% rename from python/test-data-2.0/failing/testTypesTuple1.py rename to python/test-data-2.0/extras/testTypesTuple1.py diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.err b/python/test-data-2.0/extras/testWrongKeywordArg.err similarity index 61% rename from python/test-data-2.0/failing/testWrongKeywordArg.err rename to python/test-data-2.0/extras/testWrongKeywordArg.err index ddf610a6..266a4908 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg.err +++ b/python/test-data-2.0/extras/testWrongKeywordArg.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongKeywordArg.py", line 4, in + File "test-data-2.0/extras/testWrongKeywordArg.py", line 4, in foo(x=4) WyppTypeError: unbekanntes Schlüsselwort-Argument Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`. -## Datei test-data-2.0/failing/testWrongKeywordArg.py +## Datei test-data-2.0/extras/testWrongKeywordArg.py ## Fehlerhafter Aufruf in Zeile 4: foo(x=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.out b/python/test-data-2.0/extras/testWrongKeywordArg.out similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg2.out rename to python/test-data-2.0/extras/testWrongKeywordArg.out diff --git a/python/test-data-2.0/failing/testWrongKeywordArg.py b/python/test-data-2.0/extras/testWrongKeywordArg.py similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg.py rename to python/test-data-2.0/extras/testWrongKeywordArg.py diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err b/python/test-data-2.0/extras/testWrongKeywordArg2.err similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg2.err rename to python/test-data-2.0/extras/testWrongKeywordArg2.err diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.10 b/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.10 similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg2.err-3.10 rename to python/test-data-2.0/extras/testWrongKeywordArg2.err-3.10 diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.11 b/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.11 similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg2.err-3.11 rename to python/test-data-2.0/extras/testWrongKeywordArg2.err-3.11 diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 b/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 similarity index 67% rename from python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 rename to python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 index 58014e11..d0c92f0f 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg2.err-3.12 +++ b/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongKeywordArg2.py", line 8, in + File "test-data-2.0/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 test-data-2.0/failing/testWrongKeywordArg2.py +## Datei test-data-2.0/extras/testWrongKeywordArg2.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(foo=3, y=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.out b/python/test-data-2.0/extras/testWrongKeywordArg2.out similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg3.out rename to python/test-data-2.0/extras/testWrongKeywordArg2.out diff --git a/python/test-data-2.0/failing/testWrongKeywordArg2.py b/python/test-data-2.0/extras/testWrongKeywordArg2.py similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg2.py rename to python/test-data-2.0/extras/testWrongKeywordArg2.py diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.err b/python/test-data-2.0/extras/testWrongKeywordArg3.err similarity index 67% rename from python/test-data-2.0/failing/testWrongKeywordArg3.err rename to python/test-data-2.0/extras/testWrongKeywordArg3.err index e3a60b53..38141cde 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg3.err +++ b/python/test-data-2.0/extras/testWrongKeywordArg3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongKeywordArg3.py", line 8, in + File "test-data-2.0/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 test-data-2.0/failing/testWrongKeywordArg3.py +## Datei test-data-2.0/extras/testWrongKeywordArg3.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(x=3, y=4, z=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.out b/python/test-data-2.0/extras/testWrongKeywordArg3.out similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg4.out rename to python/test-data-2.0/extras/testWrongKeywordArg3.out diff --git a/python/test-data-2.0/failing/testWrongKeywordArg3.py b/python/test-data-2.0/extras/testWrongKeywordArg3.py similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg3.py rename to python/test-data-2.0/extras/testWrongKeywordArg3.py diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.err b/python/test-data-2.0/extras/testWrongKeywordArg4.err similarity index 62% rename from python/test-data-2.0/failing/testWrongKeywordArg4.err rename to python/test-data-2.0/extras/testWrongKeywordArg4.err index fb120ce1..4d69ceeb 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg4.err +++ b/python/test-data-2.0/extras/testWrongKeywordArg4.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongKeywordArg4.py", line 4, in + File "test-data-2.0/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 test-data-2.0/failing/testWrongKeywordArg4.py +## Datei test-data-2.0/extras/testWrongKeywordArg4.py ## Fehlerhafter Aufruf in Zeile 4: foo(kw=1, x=4) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.out b/python/test-data-2.0/extras/testWrongKeywordArg4.out similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg5.out rename to python/test-data-2.0/extras/testWrongKeywordArg4.out diff --git a/python/test-data-2.0/failing/testWrongKeywordArg4.py b/python/test-data-2.0/extras/testWrongKeywordArg4.py similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg4.py rename to python/test-data-2.0/extras/testWrongKeywordArg4.py diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.err b/python/test-data-2.0/extras/testWrongKeywordArg5.err similarity index 58% rename from python/test-data-2.0/failing/testWrongKeywordArg5.err rename to python/test-data-2.0/extras/testWrongKeywordArg5.err index 18dcf6d7..16e274f9 100644 --- a/python/test-data-2.0/failing/testWrongKeywordArg5.err +++ b/python/test-data-2.0/extras/testWrongKeywordArg5.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongKeywordArg5.py", line 4, in + File "test-data-2.0/extras/testWrongKeywordArg5.py", line 4, in foo() WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 1 Argument. Gegeben: keine Argumente -## Datei test-data-2.0/failing/testWrongKeywordArg5.py +## Datei test-data-2.0/extras/testWrongKeywordArg5.py ## Aufruf in Zeile 4: foo() \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.out b/python/test-data-2.0/extras/testWrongKeywordArg5.out similarity index 100% rename from python/test-data-2.0/failing/testWrongNumOfArguments.out rename to python/test-data-2.0/extras/testWrongKeywordArg5.out diff --git a/python/test-data-2.0/failing/testWrongKeywordArg5.py b/python/test-data-2.0/extras/testWrongKeywordArg5.py similarity index 100% rename from python/test-data-2.0/failing/testWrongKeywordArg5.py rename to python/test-data-2.0/extras/testWrongKeywordArg5.py diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.err b/python/test-data-2.0/extras/testWrongNumOfArguments.err similarity index 57% rename from python/test-data-2.0/failing/testWrongNumOfArguments.err rename to python/test-data-2.0/extras/testWrongNumOfArguments.err index ad2144e7..7a9173f4 100644 --- a/python/test-data-2.0/failing/testWrongNumOfArguments.err +++ b/python/test-data-2.0/extras/testWrongNumOfArguments.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongNumOfArguments.py", line 4, in + File "test-data-2.0/extras/testWrongNumOfArguments.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/failing/testWrongNumOfArguments.py +## Datei test-data-2.0/extras/testWrongNumOfArguments.py ## Aufruf in Zeile 4: foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.out b/python/test-data-2.0/extras/testWrongNumOfArguments.out similarity index 100% rename from python/test-data-2.0/failing/testWrongNumOfArguments2.out rename to python/test-data-2.0/extras/testWrongNumOfArguments.out diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments.py b/python/test-data-2.0/extras/testWrongNumOfArguments.py similarity index 100% rename from python/test-data-2.0/failing/testWrongNumOfArguments.py rename to python/test-data-2.0/extras/testWrongNumOfArguments.py diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.err b/python/test-data-2.0/extras/testWrongNumOfArguments2.err similarity index 59% rename from python/test-data-2.0/failing/testWrongNumOfArguments2.err rename to python/test-data-2.0/extras/testWrongNumOfArguments2.err index 9ef0bb54..7f4971bc 100644 --- a/python/test-data-2.0/failing/testWrongNumOfArguments2.err +++ b/python/test-data-2.0/extras/testWrongNumOfArguments2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/testWrongNumOfArguments2.py", line 6, in + File "test-data-2.0/extras/testWrongNumOfArguments2.py", line 6, in c.foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Methode `foo` der Klasse `C` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/failing/testWrongNumOfArguments2.py +## Datei test-data-2.0/extras/testWrongNumOfArguments2.py ## Aufruf in Zeile 6: c.foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/failing/wrong-caused-by.out b/python/test-data-2.0/extras/testWrongNumOfArguments2.out similarity index 100% rename from python/test-data-2.0/failing/wrong-caused-by.out rename to python/test-data-2.0/extras/testWrongNumOfArguments2.out diff --git a/python/test-data-2.0/failing/testWrongNumOfArguments2.py b/python/test-data-2.0/extras/testWrongNumOfArguments2.py similarity index 100% rename from python/test-data-2.0/failing/testWrongNumOfArguments2.py rename to python/test-data-2.0/extras/testWrongNumOfArguments2.py diff --git a/python/test-data-2.0/failing/wrong-caused-by.err b/python/test-data-2.0/extras/wrong-caused-by.err similarity index 79% rename from python/test-data-2.0/failing/wrong-caused-by.err rename to python/test-data-2.0/extras/wrong-caused-by.err index 5077df00..1ce1f63c 100644 --- a/python/test-data-2.0/failing/wrong-caused-by.err +++ b/python/test-data-2.0/extras/wrong-caused-by.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/failing/wrong-caused-by.py", line 31, in + File "test-data-2.0/extras/wrong-caused-by.py", line 31, in mainStreetM.turnIntoStreet(redCarM) WyppTypeError: CarM(licensePlate='OG PY 123', color='rot') @@ -7,7 +7,7 @@ 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 test-data-2.0/failing/wrong-caused-by.py +## Datei test-data-2.0/extras/wrong-caused-by.py ## Fehlerhafter Aufruf in Zeile 31: mainStreetM.turnIntoStreet(redCarM) diff --git a/python/test-data-2.0/extras/wrong-caused-by.out b/python/test-data-2.0/extras/wrong-caused-by.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data-2.0/failing/wrong-caused-by.py b/python/test-data-2.0/extras/wrong-caused-by.py similarity index 100% rename from python/test-data-2.0/failing/wrong-caused-by.py rename to python/test-data-2.0/extras/wrong-caused-by.py diff --git a/python/test-data-2.0/failing/.invalidType3.py.swp b/python/test-data-2.0/failing/.invalidType3.py.swp deleted file mode 100644 index c9d32e78e7e84d6f642382f470a6a6a47da2e1fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&J#W-77zc2dwcvF1KF8W*g%kf*{{I)2b4PQQcPLH#Z;BrfduOGjazdrxo{`6gl zu+XJn_Wz^2adoYI;e4JxdN3Ykg*WkCHghK%>rT&aD06+RE4koFI_+HR)2551vuWeq z+^brynlkDT(_BTB?q)+-C{r0bm&P8e%H)%-(c`S?HlBbc1R!v!KpTs6@46W8j)u$e z-p*aRbNlepspy0N1Rwwb2tWV=5P$##{!f9}Y>6{|wQK9w+g{gO8}+)03IY&-00bZa z0SG_<0uX=z1Rwx`D=46HA%5NvV&^7rp8x+}eEO>;^_G&8MC7`l^ASp)&}3iqXu{{! z10}0D2?-JTj5eOg`~6_r!*HQ%{MFSMpI#wP4bIXAIvXBnTV zm*K6Ohndp;FNft#2(1qC;YbwT)wJlEhD_Z!AL-tzYdS4j-nO1jF9wvgE~?0z3gQnw C;))#r diff --git a/python/test-data-2.0/failing/testArgs_ok.out b/python/test-data-2.0/failing/testArgs_ok.out deleted file mode 100644 index c907ab7c..00000000 --- a/python/test-data-2.0/failing/testArgs_ok.out +++ /dev/null @@ -1 +0,0 @@ -['test-data-2.0/failing/testArgs_ok.py', 'ARG_1', 'ARG_2'] diff --git a/python/test-data-2.0/failing/testCheck_ok.out b/python/test-data-2.0/failing/testCheck_ok.out deleted file mode 100644 index 2c6e1965..00000000 --- a/python/test-data-2.0/failing/testCheck_ok.out +++ /dev/null @@ -1,5 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testCheck_ok.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-2.0/failing/testDeepEqBug_ok.out b/python/test-data-2.0/failing/testDeepEqBug_ok.out deleted file mode 100644 index d525fb31..00000000 --- a/python/test-data-2.0/failing/testDeepEqBug_ok.out +++ /dev/null @@ -1,2 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testDeepEqBug_ok.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-2.0/failing/testFunEq_ok.out b/python/test-data-2.0/failing/testFunEq_ok.out deleted file mode 100644 index 787f6567..00000000 --- a/python/test-data-2.0/failing/testFunEq_ok.out +++ /dev/null @@ -1,2 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/failing/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist -1 Test, 1 Fehler 🙁 diff --git a/python/test-data-2.0/failing/testIndexError.err b/python/test-data-2.0/failing/testIndexError.err deleted file mode 100644 index 61bd1310..00000000 --- a/python/test-data-2.0/failing/testIndexError.err +++ /dev/null @@ -1,6 +0,0 @@ -Traceback (most recent call last): - File "test-data-2.0/failing/testIndexError.py", line 6, in - foo([1,2,3]) - File "test-data-2.0/failing/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/test-data-2.0/failing/testTraceback.err b/python/test-data-2.0/failing/testTraceback.err deleted file mode 100644 index 57bc5bc9..00000000 --- a/python/test-data-2.0/failing/testTraceback.err +++ /dev/null @@ -1,6 +0,0 @@ -Traceback (most recent call last): - File "test-data-2.0/failing/testTraceback.py", line 9, in - foo(lst) - File "test-data-2.0/failing/testTraceback.py", line 7, in foo - print(lst[10]) -IndexError: list index out of range \ No newline at end of file From 3a5b793135fa3906d92a5fa7e9a4b167ca9ccad4 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 17:51:39 +0200 Subject: [PATCH 46/56] split runner --- python/TODO_nowrappers.md | 6 +- python/src/replTester.py | 2 +- python/src/runYourProgram.py | 1 + python/src/runner.py | 432 ++--------------------------------- python/src/runner2.py | 394 ++++++++++++++++++++++++++++++++ python/src/runnerCommon.py | 27 +++ 6 files changed, 442 insertions(+), 420 deletions(-) create mode 100644 python/src/runner2.py create mode 100644 python/src/runnerCommon.py diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 9d4c570e..18583ad8 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,9 +1,5 @@ -* Fix file tests in test-data-2.0 -* proper error messages for invalid @record -* Avoid absolute paths in error messages - * Integration tests - +* Python 3.12, 3.13 and 3.14? * Installation * Test windows diff --git a/python/src/replTester.py b/python/src/replTester.py index 8f68492f..9616b9c5 100644 --- a/python/src/replTester.py +++ b/python/src/replTester.py @@ -3,7 +3,7 @@ import os import argparse from dataclasses import dataclass -from runner import runCode, importTypeguard, verbose, enableVerbose +from runner2 import runCode, importTypeguard, verbose, enableVerbose usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m diff --git a/python/src/runYourProgram.py b/python/src/runYourProgram.py index 20f0d7ce..8f89a44d 100644 --- a/python/src/runYourProgram.py +++ b/python/src/runYourProgram.py @@ -1,4 +1,5 @@ # 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. diff --git a/python/src/runner.py b/python/src/runner.py index 6737e52a..89e2affc 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -1,50 +1,23 @@ +# Step 1 of the runner. Only installes typeguard and then passes control to runner2. The purpose +# of splitting the runner in two modules is to allow step2 to import typeguard. + import sys -import os -import os.path import argparse -import json -import traceback +import os +from pathlib import Path import shutil import site -import importlib -import re -import code -from modulefinder import ModuleFinder -from pathlib import Path -import subprocess -import runpy -from dataclasses import dataclass -# local imports -import stacktrace -import i18n -import paths -import instrument from myLogging import * -import errors +from runnerCommon import * -__wypp_runYourProgram = 1 - -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) - -LIB_DIR = os.path.dirname(__file__) -INSTALLED_MODULE_NAME = 'wypp' FILES_TO_INSTALL = ['writeYourProgram.py', 'drawingLib.py', '__init__.py'] - TYPEGUARD_DIR = os.path.join(LIB_DIR, "..", "deps", "typeguard") TYPEGUARD_MODULE_NAME = 'typeguard' SITELIB_DIR = os.path.join(LIB_DIR, "..", "site-lib") +__wypp_runYourProgram = 1 + class InstallMode: dontInstall = 'dontInstall' installOnly = 'installOnly' @@ -112,56 +85,15 @@ def parseCmdlineArgs(argList): 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-?' +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 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, 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)) + return True def isSameFile(f1, f2): x = readFile(f1) @@ -230,241 +162,6 @@ def installLib(mode): 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(f'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 (f := mod.__file__): # type: ignore - realp = os.path.realpath(f) - good = False - for d in realdirs: - if realp.startswith(d): - good = True - break - if good: - res.append(name) - return res - -@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 runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): - if not extraDirs: - extraDirs = [] - modDir = os.path.dirname(fileToRun) - with RunSetup(modDir, [fileToRun] + args): - with instrument.setupFinder(modDir, extraDirs, doTypecheck): - modName = os.path.basename(os.path.splitext(fileToRun)[0]) - sys.dont_write_bytecode = True # FIXME: remove? - runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=False) - -def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): - doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, 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, 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'] - 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() - -_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): - 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 DEBUG) - 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: - 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 typeguard at the top of the file because we might have to install it first. -def importTypeguard(): - global typeguard - try: - import typeguard # type: ignore - except ModuleNotFoundError as e: - printStderr(f"Module typeguard 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 - -DEBUG = False - def main(globals, argList=None): v = sys.version_info if not versionOk(v): @@ -485,13 +182,6 @@ def main(globals, argList=None): verbose(f'VERBOSE={args.verbose}, DEBUG={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) - installLib(args.installMode) if args.installMode == InstallMode.dontInstall: if SITELIB_DIR not in sys.path: @@ -503,91 +193,5 @@ def main(globals, argList=None): else: verbose(f"Adding user site-package directory {site.USER_SITE} to sys.path") sys.path.append(site.USER_SITE) - importTypeguard() - - 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, doTypecheck=args.checkTypes) - - libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes) - - - with 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 - runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, - doTypecheck=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, doTypecheck=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): - pass - - # FIXME - # 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 + import runner2 + runner2.main(globals, args, restArgs) diff --git a/python/src/runner2.py b/python/src/runner2.py new file mode 100644 index 00000000..25316450 --- /dev/null +++ b/python/src/runner2.py @@ -0,0 +1,394 @@ +# Step 2 of the runner. Assumes that typeguard has been installed + +import sys +import os +import os.path +import json +import traceback +import site +import importlib +import re +import code +from modulefinder import ModuleFinder +import subprocess +import runpy +from dataclasses import dataclass + +# local imports +from runnerCommon import * +import stacktrace +import i18n +import paths +import instrument +from myLogging import * +import errors + +__wypp_runYourProgram = 1 + +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, 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)) + + +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(f'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 (f := mod.__file__): # type: ignore + realp = os.path.realpath(f) + good = False + for d in realdirs: + if realp.startswith(d): + good = True + break + if good: + res.append(name) + return res + +@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 runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): + if not extraDirs: + extraDirs = [] + modDir = os.path.dirname(fileToRun) + with RunSetup(modDir, [fileToRun] + args): + with instrument.setupFinder(modDir, extraDirs, doTypecheck): + modName = os.path.basename(os.path.splitext(fileToRun)[0]) + sys.dont_write_bytecode = True # FIXME: remove? + runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=False) + +def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): + doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, 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, 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'] + 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() + +_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): + 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 DEBUG) + 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: + 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 typeguard at the top of the file because we might have to install it first. +def importTypeguard(): + global typeguard + try: + import typeguard # type: ignore + except ModuleNotFoundError as e: + printStderr(f"Module typeguard not found, sys.path={sys.path}: {e}") + die(1) + +DEBUG = False + +def main(globals, args, restArgs): + # assumes that runner.main has been run + + 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) + + importTypeguard() + + 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, doTypecheck=args.checkTypes) + + libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes) + + + with 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 + runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, + doTypecheck=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, doTypecheck=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): + pass + + # FIXME + # 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/src/runnerCommon.py b/python/src/runnerCommon.py new file mode 100644 index 00000000..827137fa --- /dev/null +++ b/python/src/runnerCommon.py @@ -0,0 +1,27 @@ +import os +import sys + +LIB_DIR = os.path.dirname(__file__) +INSTALLED_MODULE_NAME = 'wypp' + +__wypp_runYourProgram = 1 + +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() From 677bd4937f37cccc96e45823053ebffee26f0d07 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 21:30:07 +0200 Subject: [PATCH 47/56] fixed integration tests --- python/fileTests.py | 123 --------------- python/integration-tests/shell.py | 16 +- python/integration-tests/testIntegration.py | 100 +----------- python/src/errors.py | 48 +++--- python/src/replTester.py | 12 +- python/src/runner.py | 4 +- python/src/runner2.py | 161 +++++++++----------- python/test-data/testTypes2.py | 4 + 8 files changed, 122 insertions(+), 346 deletions(-) create mode 100644 python/test-data/testTypes2.py diff --git a/python/fileTests.py b/python/fileTests.py index 55950358..f1d334e6 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -5,126 +5,3 @@ #checkInstall('test-data/fileWithoutImport.py') #checkInstall('test-data/fileWithBothImports.py') #checkInstall('test-data/fileWithRecursiveTypes.py') -def check(file, **kws): - base = os.path.basename(file)[:-3] - d = 'test-data-2.0/extras/' - cands = [d + base + '.py', d + base + '_ok.py'] - #print(cands) - if not any([os.path.isfile(x) for x in cands]): - print(file) -def checkBoth(file, **kws): - check(file) - -checkBoth('test-data/testTraceback.py') -checkBoth('test-data/testTraceback2.py') -checkBoth('test-data/testTraceback3.py') -checkBoth('test-data/testArgs.py', exitCode=0, args=['ARG_1', 'ARG_2']) -checkBoth('test-data/printModuleName.py', exitCode=0) -checkBoth('test-data/printModuleNameImport.py', exitCode=0) -checkBoth('test-data/testTypes1.py') -checkBoth('test-data/testIndexError.py') -checkBoth('test-data/testCheckFail.py', exitCode=0) -check('test-data/testTypes2.py', exitCode=1) -check('test-data/testTypes2.py', exitCode=0, typecheck=False) -check('test-data/testABCMeta.py') -check('test-data/testClassHierarchy.py', exitCode=0) -check('test-data/testTypesCollections1.py', exitCode=0) -check('test-data/testTypesCollections2.py') -check('test-data/testTypesProtos1.py') -check('test-data/testTypesProtos2.py') -check('test-data/testTypesProtos3.py') -check('test-data/testTypesProtos4.py') -check('test-data/testTypesSubclassing1.py') -check('test-data/testTypesHigherOrderFuns.py') -check('test-data/testTypesHigherOrderFuns2.py', exitCode=0) -check('test-data/testTypesHigherOrderFuns3.py') -check('test-data/testTypesHigherOrderFuns4.py', exitCode=0) -check('test-data/testTypesHigherOrderFuns5.py', exitCode=0) -check('test-data/testTypesRecordInheritance.py') -check('test-data/testRecordSetTypes.py') -check('test-data/testRecordSetTypeForwardRef.py') -check('test-data/testForwardRef.py', exitCode=0) -check('test-data/testForwardRef1.py', exitCode=0) -check('test-data/testForwardRef2.py') -check('test-data/testForwardRef3.py', exitCode=0) -check('test-data/testForwardRef4.py') -check('test-data/testForwardRef5.py') -check('test-data/testForwardRef6.py', exitCode=0) -check('test-data/testHintParentheses1.py') -check('test-data/testHintParentheses2.py') -check('test-data/testHintParentheses3.py') -check('test-data/testTypesReturn.py') -check('test-data/testMissingReturn.py') -check('test-data/testTypesSequence1.py') -check('test-data/testTypesSequence2.py') -check('test-data/testTypesTuple1.py') -check('test-data/wrong-caused-by.py') -check('test-data/declared-at-missing.py') -check('test-data/testTypesSet1.py') -check('test-data/testTypesSet2.py') -check('test-data/testTypesSet3.py') -check('test-data/testTypesSet4.py') -check('test-data/testTypesDict1.py') -check('test-data/testTypesDict2.py') -check('test-data/testTypesDict3.py') -check('test-data/testTypesDict4.py') -check('test-data/testIterableImplicitAny.py') -check('test-data/testDoubleWrappingDicts.py', exitCode=0) -check('test-data/testClassRecursion.py', exitCode=0) -check('test-data/testWrongNumOfArguments.py') -check('test-data/testWrongNumOfArguments2.py') -check('test-data/testLiteralInstanceOf.py', exitCode=0) -check('test-data/testWrongKeywordArg.py') -check('test-data/testWrongKeywordArg2.py') -check('test-data/testComplex.py', exitCode=0) -check('test-data/testUnionLiteral.py', exitCode=0) -check('test-data/testCheck.py', exitCode=0) -check('test-data/testNameErrorBug.py', exitCode=0) -check('test-data/testOriginalTypeNames.py', exitCode=0) -check('test-data/testDeepEqBug.py', exitCode=0) -check('test-data/testLockFactory.py') -check('test-data/testLockFactory2.py') -check('test-data/testGetSource.py') -check('test-data/testDict.py', exitCode=0) -check('test-data/testFunEq.py', exitCode=0) -check('test-data/testBugSliceIndices.py', exitCode=0) -check('test-data/testABC.py', exitCode=0) -check('test-data/testTypesWrapperEq.py', exitCode=0) -check('test-data/testTypesProtos5.py', exitCode=0) -check('test-data/testTypesProtos6.py') -check('test-data/testTypesProtos7.py') -check('test-data/testTypesProtos8.py') -check('test-data/testTypesProtos9.py') -check('test-data/testIterable1.py', exitCode=0) -check('test-data/testIterable2.py', exitCode=0) -check('test-data/testIterable3.py', exitCode=0) -check('test-data/testIterable4.py', exitCode=0) -check('test-data/testIterable5.py', exitCode=0) -check('test-data/testIterable6.py', exitCode=0) -check('test-data/testIterable7.py', exitCode=0) -check('test-data/testIterator.py', exitCode=0) -check('test-data/testIterator2.py', exitCode=0) -check('test-data/testIterator3.py', exitCode=0) -check('test-data/testIterator4.py', exitCode=0) -check('test-data/testConcat.py', exitCode=0) -check('test-data/testCopy.py', exitCode=0) -check('test-data/testHof.py', exitCode=0) -check('test-data/testIndexSeq.py', exitCode=0) -check('test-data/testTodo.py') -check('test-data/testImpossible.py') -check('test-data/testInvalidLiteral.py') -check('test-data/admin.py', exitCode=0) -check('test-data/modules/A/main.py', args=['--extra-dir', 'test-data/modules/B'], - pythonPath=['test-data/modules/B']) -check('test-data/testUnion.py', exitCode=0) -check('test-data/testUnion2.py', exitCode=0) -check('test-data/testUnion3.py', exitCode=0) -check('test-data/testLiteral1.py') -check('test-data/testForwardTypeInRecord.py', exitCode=0) -check('test-data/testForwardTypeInRecord2.py', exitCode=0) -check('test-data/testUnionOfUnion.py', exitCode=0) -check('test-data/testRecordTypes.py') -#check('test-data/testDisappearingObject_01.py', exitCode=0) -#check('test-data/testDisappearingObject_02.py', exitCode=0) -#check('test-data/testDisappearingObject_03.py', exitCode=0) -checkBoth('test-data/testTypeKeyword.py', exitCode=0, minVersion=(3,12)) 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..884c34ee 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -33,74 +33,6 @@ 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'] @@ -143,37 +75,11 @@ def test_types1(self): 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) + 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) + 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) @@ -185,4 +91,4 @@ 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' 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/src/errors.py b/python/src/errors.py index 670cec5f..90cdde07 100644 --- a/python/src/errors.py +++ b/python/src/errors.py @@ -84,11 +84,11 @@ def unknownKeywordArgument(callableName: location.CallableName, callLoc: Optiona lines.append(i18n.tr('unknown keyword argument')) lines.append('') lines.append(i18n.unknownKeywordArgument(callableName, name)) - if callLoc: + 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(renderLoc(callLoc)) + lines.append(callLocR) raise WyppTypeError('\n'.join(lines)) @staticmethod @@ -96,10 +96,10 @@ def invalidRecordAnnotation(loc: Optional[location.Loc]) -> WyppTypeError: lines = [] lines.append(i18n.tr('invalid record definition')) lines.append('') - if loc: + 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(renderLoc(loc)) + lines.append(locR) raise WyppTypeError('\n'.join(lines)) @staticmethod @@ -128,20 +128,20 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc if shouldReportTyMismatch(resultTy, givenTy): lines.append(i18n.wrongReturnValue(renderTy(givenTy))) printedFileName = None - if resultTypeLoc: + 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(renderLoc(resultTypeLoc)) - if givenValue is not None and returnLoc: + 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(renderLoc(returnLoc)) - if callLoc: + lines.append(returnLocR) + if callLoc and (callLocR := renderLoc(callLoc)): lines.append('') if printedFileName != callLoc.filename: lines.append(f'## {i18n.tr("File")} {callLoc.filename}') @@ -149,7 +149,7 @@ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[loc lines.append(f'## {i18n.unexpectedNoReturn(callLoc.startLine)}\n') else: lines.append(f'## {i18n.unexpectedReturn(callLoc.startLine)}\n') - lines.append(renderLoc(callLoc)) + lines.append(callLocR) raise WyppTypeError('\n'.join(lines), extraFrames) @staticmethod @@ -166,17 +166,17 @@ def argumentError(callableName: location.CallableName, paramName: str, paramInde lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramName)) if shouldReportTyMismatch(paramTy, type(givenValue)): lines.append(i18n.realArgumentTy(renderTy(type(givenValue)))) - if givenLoc: + 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(renderLoc(givenLoc)) - if paramLoc: + 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(renderLoc(paramLoc)) + lines.append(paramLocR) raise WyppTypeError('\n'.join(lines)) @staticmethod @@ -189,11 +189,11 @@ def defaultError(callableName: location.CallableName, paramName: str, paramLoc: lines.append(i18n.expectingDefaultValueOfTy(callableName, renderTy(paramTy), paramName)) if shouldReportTyMismatch(paramTy, type(givenValue)): lines.append(i18n.realDefaultValueTy(renderTy(type(givenValue)))) - if paramLoc: + 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(renderLoc(paramLoc)) + lines.append(paramLocR) raise WyppTypeError('\n'.join(lines)) @staticmethod @@ -213,22 +213,22 @@ def argCountMismatch(callableName: location.CallableName, callLoc: Optional[loca else: lines.append(i18n.argCountMax(callableName, numParams)) lines.append(i18n.tr('Given: ') + i18n.argCount(numArgs)) - if callLoc: + 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(renderLoc(callLoc)) + 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: + 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(renderLoc(paramLoc)) + lines.append(paramLocR) raise WyppTypeError('\n'.join(lines)) @staticmethod @@ -249,17 +249,17 @@ def recordAssignError(recordName: str, lines.append(i18n.recordAttrDeclTy(recordName, attrName, renderTy(attrTy))) if shouldReportTyMismatch(attrTy, type(setterValue)): lines.append(i18n.realSetAttrTy(renderTy(type(setterValue)))) - if setterLoc: + 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(renderLoc(setterLoc)) - if attrLoc: + 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(renderLoc(attrLoc)) + lines.append(attrLocR) raise WyppTypeError('\n'.join(lines)) class WyppAttributeError(AttributeError, WyppError): diff --git a/python/src/replTester.py b/python/src/replTester.py index 9616b9c5..9368aa70 100644 --- a/python/src/replTester.py +++ b/python/src/replTester.py @@ -3,7 +3,7 @@ import os import argparse from dataclasses import dataclass -from runner2 import runCode, importTypeguard, verbose, enableVerbose +from myLogging import * usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m @@ -54,10 +54,14 @@ def parseCmdlineArgs(): enableVerbose() libDir = os.path.dirname(__file__) +siteDir = os.path.join(libDir, "..", "site-lib") +if siteDir not in sys.path: + sys.path.insert(0, siteDir) libFile = os.path.join(libDir, 'writeYourProgram.py') defs = globals() + +from runner2 import runCode, importTypeguard importTypeguard() -# runCode(libFile, defs, []) for lib in opts.libs: d = os.path.dirname(lib) @@ -66,7 +70,7 @@ def parseCmdlineArgs(): for lib in opts.libs: verbose(f"Loading lib {lib}") - runCode(lib, defs, []) + defs = runCode(lib, defs) totalFailures = 0 totalTests = 0 @@ -111,7 +115,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/src/runner.py b/python/src/runner.py index 89e2affc..0bf86d56 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -176,11 +176,9 @@ def main(globals, argList=None): if args.verbose: enableVerbose() if args.debug: - global DEBUG - DEBUG = True enableDebug() - verbose(f'VERBOSE={args.verbose}, DEBUG={DEBUG}') + verbose(f'VERBOSE={args.verbose}, DEBUG={args.debug}') installLib(args.installMode) if args.installMode == InstallMode.dontInstall: diff --git a/python/src/runner2.py b/python/src/runner2.py index 25316450..5feae112 100644 --- a/python/src/runner2.py +++ b/python/src/runner2.py @@ -1,11 +1,11 @@ -# Step 2 of the runner. Assumes that typeguard has been installed +# Step 2 of the runner. Assumes that typeguard has been installed so that this +# module can use it. import sys import os -import os.path import json import traceback -import site +import argparse import importlib import re import code @@ -133,39 +133,17 @@ def findImportedModules(path, file): res.append(name) return res -@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 runCode(fileToRun, globals, args, doTypecheck=True, extraDirs=None): +def runCode(fileToRun, globals, doTypecheck=True, extraDirs=None) -> dict: if not extraDirs: extraDirs = [] - modDir = os.path.dirname(fileToRun) - with RunSetup(modDir, [fileToRun] + args): - with instrument.setupFinder(modDir, extraDirs, doTypecheck): - modName = os.path.basename(os.path.splitext(fileToRun)[0]) - sys.dont_write_bytecode = True # FIXME: remove? - runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=False) - -def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True, extraDirs=None): - doRun = lambda: runCode(fileToRun, globals, args, doTypecheck=doTypecheck, 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 # FIXME: remove? + 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() @@ -174,7 +152,7 @@ def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, doTypecheck=True handleCurrentException() else: die(0) - doRun() + return doRun() # globals already contain libDefs def runTestsInFile(testFile, globals, libDefs, doTypecheck=True, extraDirs=[]): @@ -182,7 +160,7 @@ def runTestsInFile(testFile, globals, libDefs, doTypecheck=True, extraDirs=[]): printStderr(f"Running tutor's tests in {testFile}") libDefs.resetTestCount() try: - runCode(testFile, globals, [], doTypecheck=doTypecheck, extraDirs=extraDirs) + runCode(testFile, globals, doTypecheck=doTypecheck, extraDirs=extraDirs) except: handleCurrentException() return libDefs.dict['printTestResults']('Tutor: ') @@ -250,7 +228,7 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): 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 DEBUG) + stackSummary = stacktrace.limitTraceback(frameList, extra, not isBug and not isDebug()) header = False for x in stackSummary.format(): if not header: @@ -292,9 +270,28 @@ def importTypeguard(): printStderr(f"Module typeguard not found, sys.path={sys.path}: {e}") die(1) -DEBUG = False +@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 main(globals, args, restArgs): +def main(globals: dict, args: argparse.Namespace, restArgs: list[str]): # assumes that runner.main has been run if args.lang: @@ -306,7 +303,7 @@ def main(globals, args, restArgs): importTypeguard() - fileToRun = args.file + fileToRun: str = args.file if args.changeDir: os.chdir(os.path.dirname(fileToRun)) fileToRun = os.path.basename(fileToRun) @@ -318,21 +315,22 @@ def main(globals, args, restArgs): if fileToRun is None: return + if not args.checkRunnable and (not args.quiet or args.verbose): printWelcomeString(fileToRun, version, doTypecheck=args.checkTypes) libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes) - - with paths.projectDir(os.path.abspath(os.getcwd())): + with (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 - runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, - doTypecheck=args.checkTypes, extraDirs=args.extraDirs) + globals = runStudentCode(fileToRun, globals, args.checkRunnable, + doTypecheck=args.checkTypes, extraDirs=args.extraDirs) except Exception as e: verbose(f'Error while running code in {fileToRun}: {e}') handleCurrentException(exit=not isInteractive) @@ -341,54 +339,31 @@ def main(globals, args, restArgs): performChecks(args.check, args.testFile, globals, libDefs, doTypecheck=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) + 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): - pass - - # FIXME - # 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 + def showtraceback(self) -> None: + handleCurrentException(exit=False, removeFirstTb=True, file=sys.stdout) + diff --git a/python/test-data/testTypes2.py b/python/test-data/testTypes2.py new file mode 100644 index 00000000..593392d3 --- /dev/null +++ b/python/test-data/testTypes2.py @@ -0,0 +1,4 @@ +def inc(x: int) -> int: + return x + +inc("1") From 68649693fe147cab5a8cf685f9245e23c618089c Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 21:35:26 +0200 Subject: [PATCH 48/56] fixed import tests --- python/fileTests-2.0.py | 4 ++++ python/fileTests.py | 9 +++------ .../imports}/fileWithBothImports.py | 0 .../imports}/fileWithImport.py | 0 .../imports}/fileWithoutImport.py | 0 python/{test-data => test-data-2.0/imports}/localMod.py | 0 6 files changed, 7 insertions(+), 6 deletions(-) rename python/{test-data => test-data-2.0/imports}/fileWithBothImports.py (100%) rename python/{test-data => test-data-2.0/imports}/fileWithImport.py (100%) rename python/{test-data => test-data-2.0/imports}/fileWithoutImport.py (100%) rename python/{test-data => test-data-2.0/imports}/localMod.py (100%) diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py index 662a9cb4..d1972bb1 100644 --- a/python/fileTests-2.0.py +++ b/python/fileTests-2.0.py @@ -7,6 +7,10 @@ #directories = [Path("test-data-2.0/basics")] #directories = [Path("test-data-2.0/extras")] +checkInstall('test-data-2.0/imports/fileWithImport.py') +checkInstall('test-data-2.0/imports/fileWithoutImport.py') +checkInstall('test-data-2.0/imports/fileWithBothImports.py') + for d in directories: for file in d.iterdir(): if file.is_file(): diff --git a/python/fileTests.py b/python/fileTests.py index f1d334e6..5366f622 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -1,7 +1,4 @@ -# from fileTestsLib import * -import os +from fileTestsLib import * + + -#checkInstall('test-data/fileWithImport.py') -#checkInstall('test-data/fileWithoutImport.py') -#checkInstall('test-data/fileWithBothImports.py') -#checkInstall('test-data/fileWithRecursiveTypes.py') diff --git a/python/test-data/fileWithBothImports.py b/python/test-data-2.0/imports/fileWithBothImports.py similarity index 100% rename from python/test-data/fileWithBothImports.py rename to python/test-data-2.0/imports/fileWithBothImports.py diff --git a/python/test-data/fileWithImport.py b/python/test-data-2.0/imports/fileWithImport.py similarity index 100% rename from python/test-data/fileWithImport.py rename to python/test-data-2.0/imports/fileWithImport.py diff --git a/python/test-data/fileWithoutImport.py b/python/test-data-2.0/imports/fileWithoutImport.py similarity index 100% rename from python/test-data/fileWithoutImport.py rename to python/test-data-2.0/imports/fileWithoutImport.py diff --git a/python/test-data/localMod.py b/python/test-data-2.0/imports/localMod.py similarity index 100% rename from python/test-data/localMod.py rename to python/test-data-2.0/imports/localMod.py From eaa227688614006811b265aa5ed32821afbdae66 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 23 Sep 2025 21:42:19 +0200 Subject: [PATCH 49/56] all tests working for python 3.12 --- .../basics/async.err | 6 +- .../basics/async.err_en | 6 +- .../basics/async.out | 0 .../basics/async.out_en | 0 .../basics/async.py | 0 .../basics/constructor.err | 4 +- .../basics/constructor.err_en | 4 +- .../basics/constructor.out | 0 .../basics/constructor.out_en | 0 .../basics/constructor.py | 0 .../basics/constructorDataclass_ok.py | 0 .../basics/constructor_ok.err | 0 .../basics/constructor_ok.out | 0 .../basics/constructor_ok.py | 0 .../basics/forwardRefs.err | 4 +- .../basics/forwardRefs.err_en | 4 +- .../basics/forwardRefs.out | 0 .../basics/forwardRefs.out_en | 0 .../basics/forwardRefs.py | 0 .../basics/forwardRefs_ok.err | 0 .../basics/forwardRefs_ok.out | 0 .../basics/forwardRefs_ok.py | 0 .../basics/functionArg.err | 4 +- .../basics/functionArg.err_en | 4 +- .../basics/functionArg.out | 0 .../basics/functionArg.out_en | 0 .../basics/functionArg.py | 0 .../basics/functionArg_ok.err | 0 .../basics/functionArg_ok.err_None | 0 .../basics/functionArg_ok.out | 0 .../basics/functionArg_ok.out_None | 0 .../basics/functionArg_ok.py | 0 .../basics/functionNoResult.err | 6 +- .../basics/functionNoResult.err_en | 6 +- .../basics/functionNoResult.out | 0 .../basics/functionNoResult.out_en | 0 .../basics/functionNoResult.py | 0 .../basics/functionNoResult2.err | 6 +- .../basics/functionNoResult2.err_en | 6 +- .../basics/functionNoResult2.out | 0 .../basics/functionNoResult2.out_en | 0 .../basics/functionNoResult2.py | 0 .../basics/functionNoResult2_ok.err | 0 .../basics/functionNoResult2_ok.out | 0 .../basics/functionNoResult2_ok.py | 0 .../basics/functionNoResult3.err | 6 +- .../basics/functionNoResult3.err_en | 6 +- .../basics/functionNoResult3.out | 0 .../basics/functionNoResult3.out_en | 0 .../basics/functionNoResult3.py | 0 .../basics/functionNoResult3_ok.err | 0 .../basics/functionNoResult3_ok.out | 0 .../basics/functionNoResult3_ok.py | 0 .../basics/functionNoResult_ok.err | 0 .../basics/functionNoResult_ok.out | 0 .../basics/functionNoResult_ok.py | 0 .../basics/functionResult.err | 6 +- .../basics/functionResult.err_en | 6 +- .../basics/functionResult.out | 0 .../basics/functionResult.out_en | 0 .../basics/functionResult.py | 0 .../basics/functionResult2.err | 6 +- .../basics/functionResult2.err_en | 6 +- .../basics/functionResult2.out | 0 .../basics/functionResult2.out_en | 0 .../basics/functionResult2.py | 0 .../basics/functionResult_ok.err | 0 .../basics/functionResult_ok.out | 0 .../basics/functionResult_ok.py | 0 .../basics/iterator.err | 6 +- .../basics/iterator.err_en | 6 +- .../basics/iterator.out | 0 .../basics/iterator.out_en | 0 .../basics/iterator.py | 0 .../basics/iterator_ok.err | 0 .../basics/iterator_ok.out | 0 .../basics/iterator_ok.py | 0 .../basics/kwargs.err | 4 +- .../basics/kwargs.err_en | 4 +- .../basics/kwargs.out | 0 .../basics/kwargs.out_en | 0 .../basics/kwargs.py | 0 .../basics/kwargs2.err | 4 +- .../basics/kwargs2.err_en | 4 +- .../basics/kwargs2.out | 0 .../basics/kwargs2.out_en | 0 .../basics/kwargs2.py | 0 .../basics/kwargs_ok.err | 0 .../basics/kwargs_ok.out | 0 .../basics/kwargs_ok.py | 0 .../basics/listArg.err | 4 +- .../basics/listArg.err_en | 4 +- .../basics/listArg.out | 0 .../basics/listArg.out_en | 0 .../basics/listArg.py | 0 .../basics/listArg_ok.err | 0 .../basics/listArg_ok.out | 0 .../basics/listArg_ok.py | 0 .../basics/listResult.err | 6 +- .../basics/listResult.err_en | 6 +- .../basics/listResult.out | 0 .../basics/listResult.out_en | 0 .../basics/listResult.py | 0 .../basics/listResult_ok.err | 0 .../basics/listResult_ok.out | 0 .../basics/listResult_ok.py | 0 .../basics/method.err | 4 +- .../basics/method.err_en | 4 +- .../basics/method.out | 0 .../basics/method.out_en | 0 .../basics/method.py | 0 .../basics/method_ok.err | 0 .../basics/method_ok.out | 0 .../basics/method_ok.py | 0 .../basics/mutable.err | 4 +- .../basics/mutable.err_en | 4 +- .../basics/mutable.out | 0 .../basics/mutable.out_en | 0 .../basics/mutable.py | 0 .../basics/mutable2.err | 4 +- .../basics/mutable2.err_en | 4 +- .../basics/mutable2.out | 0 .../basics/mutable2.out_en | 0 .../basics/mutable2.py | 0 .../basics/mutable2_ok.err | 0 .../basics/mutable2_ok.out | 0 .../basics/mutable2_ok.py | 0 .../basics/mutable_ok.err | 0 .../basics/mutable_ok.out | 0 .../basics/mutable_ok.py | 0 .../basics/nested.err | 4 +- .../basics/nested.out | 0 .../basics/nested.py | 0 .../basics/nestedFun.err | 6 +- .../basics/nestedFun.err_en | 6 +- .../basics/nestedFun.out | 0 .../basics/nestedFun.out_en | 0 .../basics/nestedFun.py | 0 .../basics/nosig_ok.err | 0 .../basics/nosig_ok.out | 0 .../basics/nosig_ok.py | 0 .../basics/optionalArgs.err | 4 +- .../basics/optionalArgs.err_en | 4 +- .../basics/optionalArgs.out | 0 .../basics/optionalArgs.out_en | 0 .../basics/optionalArgs.py | 0 .../basics/optionalArgs2.err | 4 +- .../basics/optionalArgs2.err_en | 4 +- .../basics/optionalArgs2.out | 0 .../basics/optionalArgs2.out_en | 0 .../basics/optionalArgs2.py | 0 .../basics/optionalArgs3.err | 4 +- .../basics/optionalArgs3.err_en | 4 +- .../basics/optionalArgs3.out | 0 .../basics/optionalArgs3.out_en | 0 .../basics/optionalArgs3.py | 0 .../basics/optionalArgs4.err | 6 +- .../basics/optionalArgs4.err_en | 6 +- .../basics/optionalArgs4.out | 0 .../basics/optionalArgs4.out_en | 0 .../basics/optionalArgs4.py | 0 .../basics/optionalArgs_ok.err | 0 .../basics/optionalArgs_ok.out | 0 .../basics/optionalArgs_ok.py | 0 .../basics/optionalAttr.err | 4 +- .../basics/optionalAttr.err_en | 4 +- .../basics/optionalAttr.out | 0 .../basics/optionalAttr.out_en | 0 .../basics/optionalAttr.py | 0 .../basics/optionalAttr2.err | 4 +- .../basics/optionalAttr2.err_en | 4 +- .../basics/optionalAttr2.out | 0 .../basics/optionalAttr2.out_en | 0 .../basics/optionalAttr2.py | 0 .../basics/optionalAttr3.err | 4 +- .../basics/optionalAttr3.err_en | 4 +- .../basics/optionalAttr3.out | 0 .../basics/optionalAttr3.out_en | 0 .../basics/optionalAttr3.py | 0 .../basics/optionalAttr_ok.err | 0 .../basics/optionalAttr_ok.out | 0 .../basics/optionalAttr_ok.py | 0 .../basics/partial.err | 4 +- .../basics/partial.err_en | 4 +- .../basics/partial.out | 0 .../basics/partial.out_en | 0 .../basics/partial.py | 0 .../basics/record.err | 4 +- .../basics/record.err_en | 4 +- .../basics/record.out | 0 .../basics/record.out_en | 0 .../basics/record.py | 0 .../basics/record_ok.err | 0 .../basics/record_ok.out | 0 .../basics/record_ok.py | 0 .../basics/sample_ok.err | 0 .../basics/sample_ok.out | 0 .../basics/sample_ok.py | 0 python/file-test-data/basics/stack.err | 13 ++ .../basics/stack.out | 0 .../basics/stack.py | 0 .../basics/staticmethod.err | 4 +- .../basics/staticmethod.err_en | 4 +- .../basics/staticmethod.out | 0 .../basics/staticmethod.out_en | 0 .../basics/staticmethod.py | 0 .../basics/staticmethod_ok.err | 0 .../basics/staticmethod_ok.out | 0 .../basics/staticmethod_ok.py | 0 .../basics/testCallable.err | 4 +- .../basics/testCallable.out | 0 .../basics/testCallable.py | 0 .../basics/tooFewArgs.err | 4 +- .../basics/tooFewArgs.out | 0 .../basics/tooFewArgs.py | 0 .../basics/tooFewAttrs.err | 4 +- .../basics/tooFewAttrs.out | 0 .../basics/tooFewAttrs.py | 0 .../basics/tooManyArgs.err | 4 +- .../basics/tooManyArgs.err_en | 4 +- .../basics/tooManyArgs.out | 0 .../basics/tooManyArgs.out_en | 0 .../basics/tooManyArgs.py | 0 .../basics/tooManyAttrs.err | 4 +- .../basics/tooManyAttrs.err_en | 4 +- .../basics/tooManyAttrs.out | 0 .../basics/tooManyAttrs.out_en | 0 .../basics/tooManyAttrs.py | 0 .../basics/typeAlias.err | 4 +- .../basics/typeAlias.err_en | 4 +- .../basics/typeAlias.out | 0 .../basics/typeAlias.out_en | 0 .../basics/typeAlias.py | 0 .../basics/typeAlias2.err | 4 +- .../basics/typeAlias2.err_en | 4 +- .../basics/typeAlias2.out | 0 .../basics/typeAlias2.out_en | 0 .../basics/typeAlias2.py | 0 .../extras/admin_ok.err | 0 .../extras/admin_ok.out | 0 .../extras/admin_ok.py | 0 .../extras/declared-at-missing.err | 0 .../extras/declared-at-missing.err-3.10 | 0 .../extras/declared-at-missing.err-3.11 | 0 .../extras/declared-at-missing.err-3.12 | 4 +- .../extras/declared-at-missing.out | 0 .../extras/declared-at-missing.py | 0 .../extras/invalidRecord.err | 2 +- .../extras/invalidRecord.out | 0 .../extras/invalidRecord.py | 0 .../extras/invalidType.err | 4 +- .../extras/invalidType.out | 0 .../extras/invalidType.py | 0 .../extras/invalidType2.err | 2 +- .../extras/invalidType2.out | 0 .../extras/invalidType2.py | 0 .../extras/invalidType3.err | 4 +- .../extras/invalidType3.out | 0 .../extras/invalidType3.py | 0 .../extras/invalidType4.err | 4 +- .../extras/invalidType4.out | 0 .../extras/invalidType4.py | 0 .../extras/main.err | 6 +- .../extras/main.out | 0 python/file-test-data/extras/main.py | 4 + .../extras/printModuleNameImport_ok.err | 0 .../extras/printModuleNameImport_ok.out | 0 .../extras/printModuleNameImport_ok.py | 0 .../extras/printModuleName_ok.err | 0 .../extras/printModuleName_ok.out | 0 .../extras/printModuleName_ok.py | 0 .../extras/testABCMeta.err | 2 +- .../extras/testABCMeta.err-3.10 | 0 .../extras/testABCMeta.err-3.11 | 0 .../extras/testABCMeta.out | 0 .../extras/testABCMeta.py | 0 .../extras/testABC_ok.err | 0 .../extras/testABC_ok.out | 0 .../extras/testABC_ok.py | 0 .../extras/testArgs_ok.err | 0 python/file-test-data/extras/testArgs_ok.out | 1 + .../extras/testArgs_ok.py | 0 .../extras/testBugSliceIndices_ok.err | 0 .../extras/testBugSliceIndices_ok.out | 0 .../extras/testBugSliceIndices_ok.py | 0 .../extras/testCheckFail_ok.err | 0 .../extras/testCheckFail_ok.out | 0 .../extras/testCheckFail_ok.py | 0 .../extras/testCheck_ok.err | 0 python/file-test-data/extras/testCheck_ok.out | 5 + .../extras/testCheck_ok.py | 0 .../extras/testClassHierarchy_ok.err | 0 .../extras/testClassHierarchy_ok.out | 0 .../extras/testClassHierarchy_ok.py | 0 .../extras/testClassRecursion_ok.err | 0 .../extras/testClassRecursion_ok.out | 0 .../extras/testClassRecursion_ok.py | 0 .../extras/testComplex_ok.err | 0 .../extras/testComplex_ok.out | 0 .../extras/testComplex_ok.py | 0 .../extras/testConcat_ok.err | 0 .../extras/testConcat_ok.out | 0 .../extras/testConcat_ok.py | 0 .../extras/testCopy_ok.err | 0 .../extras/testCopy_ok.out | 0 .../extras/testCopy_ok.py | 0 .../extras/testDeepEqBug_ok.err | 0 .../extras/testDeepEqBug_ok.out | 2 + .../extras/testDeepEqBug_ok.py | 0 .../extras/testDict_ok.err | 0 .../extras/testDict_ok.out | 0 .../extras/testDict_ok.py | 0 .../extras/testDoubleWrappingDicts_ok.err | 0 .../extras/testDoubleWrappingDicts_ok.out | 0 .../extras/testDoubleWrappingDicts_ok.py | 0 .../extras/testForwardRef1_ok.err | 0 .../extras/testForwardRef1_ok.out | 0 .../extras/testForwardRef1_ok.py | 0 .../extras/testForwardRef2.err | 4 +- .../extras/testForwardRef2.out | 0 .../extras/testForwardRef2.py | 0 .../extras/testForwardRef3_ok.err | 0 .../extras/testForwardRef3_ok.out | 0 .../extras/testForwardRef3_ok.py | 0 .../extras/testForwardRef4.err | 0 .../extras/testForwardRef4.err-3.10 | 0 .../extras/testForwardRef4.err-3.11 | 0 .../extras/testForwardRef4.err-3.12 | 4 +- .../extras/testForwardRef4.out | 0 .../extras/testForwardRef4.py | 0 .../extras/testForwardRef5.err | 0 .../extras/testForwardRef5.err-3.10 | 0 .../extras/testForwardRef5.err-3.11 | 0 .../extras/testForwardRef5.err-3.12 | 4 +- .../extras/testForwardRef5.out | 0 .../extras/testForwardRef5.py | 0 .../extras/testForwardRef6_ok.err | 0 .../extras/testForwardRef6_ok.out | 0 .../extras/testForwardRef6_ok.py | 0 .../extras/testForwardRef_ok.err | 0 .../extras/testForwardRef_ok.out | 0 .../extras/testForwardRef_ok.py | 0 .../extras/testForwardTypeInRecord2_ok.err | 0 .../extras/testForwardTypeInRecord2_ok.out | 0 .../extras/testForwardTypeInRecord2_ok.py | 0 .../extras/testForwardTypeInRecord_ok.err | 0 .../extras/testForwardTypeInRecord_ok.out | 0 .../extras/testForwardTypeInRecord_ok.py | 0 .../extras/testFunEq_ok.err | 0 python/file-test-data/extras/testFunEq_ok.out | 2 + .../extras/testFunEq_ok.py | 0 .../extras/testGetSource.err | 4 +- .../extras/testGetSource.out | 0 .../extras/testGetSource.py | 0 .../extras/testHintParentheses1.err | 4 +- .../extras/testHintParentheses1.out | 0 .../extras/testHintParentheses1.py | 0 .../extras/testHintParentheses2.err | 4 +- .../extras/testHintParentheses2.out | 0 .../extras/testHintParentheses2.py | 0 .../extras/testHintParentheses3.err | 4 +- .../extras/testHintParentheses3.out | 0 .../extras/testHintParentheses3.py | 0 .../extras/testHof_ok.err | 0 .../extras/testHof_ok.out | 0 .../extras/testHof_ok.py | 0 .../extras/testImpossible.err | 2 +- .../extras/testImpossible.out | 0 .../extras/testImpossible.py | 0 .../file-test-data/extras/testIndexError.err | 6 + .../extras/testIndexError.out | 0 .../extras/testIndexError.py | 0 .../extras/testIndexSeq_ok.err | 0 .../extras/testIndexSeq_ok.out | 0 .../extras/testIndexSeq_ok.py | 0 .../extras/testInvalidLiteral.err | 4 +- .../extras/testInvalidLiteral.out | 0 .../extras/testInvalidLiteral.py | 0 .../extras/testIterable1_ok.err | 0 .../extras/testIterable1_ok.out | 0 .../extras/testIterable1_ok.py | 0 .../extras/testIterable2_ok.err | 0 .../extras/testIterable2_ok.out | 0 .../extras/testIterable2_ok.py | 0 .../extras/testIterable3_ok.err | 0 .../extras/testIterable3_ok.out | 0 .../extras/testIterable3_ok.py | 0 .../extras/testIterable4_ok.err | 0 .../extras/testIterable4_ok.out | 0 .../extras/testIterable4_ok.py | 0 .../extras/testIterable5_ok.err | 0 .../extras/testIterable5_ok.out | 0 .../extras/testIterable5_ok.py | 0 .../extras/testIterable6_ok.err | 0 .../extras/testIterable6_ok.out | 0 .../extras/testIterable6_ok.py | 0 .../extras/testIterable7_ok.err | 0 .../extras/testIterable7_ok.out | 0 .../extras/testIterable7_ok.py | 0 .../extras/testIterableImplicitAny.err | 4 +- .../extras/testIterableImplicitAny.out | 0 .../extras/testIterableImplicitAny.py | 0 .../extras/testIterator2_ok.err | 0 .../extras/testIterator2_ok.out | 0 .../extras/testIterator2_ok.py | 0 .../extras/testIterator3_ok.err | 0 .../extras/testIterator3_ok.out | 0 .../extras/testIterator3_ok.py | 0 .../extras/testIterator4_ok.err | 0 .../extras/testIterator4_ok.out | 0 .../extras/testIterator4_ok.py | 0 .../extras/testIterator_ok.err | 0 .../extras/testIterator_ok.out | 0 .../extras/testIterator_ok.py | 0 .../extras/testLiteral1.err | 4 +- .../extras/testLiteral1.out | 0 .../extras/testLiteral1.py | 0 .../extras/testLiteralInstanceOf_ok.err | 0 .../extras/testLiteralInstanceOf_ok.out | 0 .../extras/testLiteralInstanceOf_ok.py | 0 .../extras/testLockFactory.err | 4 +- .../extras/testLockFactory.out | 0 .../extras/testLockFactory.py | 0 .../extras/testLockFactory2.err | 4 +- .../extras/testLockFactory2.out | 0 .../extras/testLockFactory2.py | 0 .../extras/testLockFactory_ok.err | 0 .../extras/testLockFactory_ok.out | 0 .../extras/testLockFactory_ok.py | 0 .../extras/testMissingReturn.err | 6 +- .../extras/testMissingReturn.out | 0 .../extras/testMissingReturn.py | 0 .../extras/testNameErrorBug_ok.err | 0 .../extras/testNameErrorBug_ok.out | 0 .../extras/testNameErrorBug_ok.py | 0 .../extras/testOriginalTypeNames_ok.err | 0 .../extras/testOriginalTypeNames_ok.out | 0 .../extras/testOriginalTypeNames_ok.py | 0 .../extras/testRecordSetTypeForwardRef.err | 0 .../testRecordSetTypeForwardRef.err-3.10 | 0 .../testRecordSetTypeForwardRef.err-3.11 | 0 .../testRecordSetTypeForwardRef.err-3.12 | 6 +- .../extras/testRecordSetTypeForwardRef.out | 0 .../extras/testRecordSetTypeForwardRef.py | 0 .../extras/testRecordSetTypes.err | 0 .../extras/testRecordSetTypes.err-3.10 | 0 .../extras/testRecordSetTypes.err-3.11 | 0 .../extras/testRecordSetTypes.err-3.12 | 6 +- .../extras/testRecordSetTypes.out | 0 .../extras/testRecordSetTypes.py | 0 .../extras/testRecordTypes.err | 0 .../extras/testRecordTypes.err-3.12 | 4 +- .../extras/testRecordTypes.out | 0 .../extras/testRecordTypes.py | 0 .../extras/testTodo.err | 2 +- .../extras/testTodo.out | 0 .../extras/testTodo.py | 0 .../file-test-data/extras/testTraceback.err | 6 + .../extras/testTraceback.out | 0 .../extras/testTraceback.py | 0 .../file-test-data/extras/testTraceback2.err | 4 + .../extras/testTraceback2.err-3.10.0 | 0 .../extras/testTraceback2.err-3.9 | 0 .../extras/testTraceback2.out | 0 .../extras/testTraceback2.py | 0 .../extras/testTraceback3.err | 2 +- .../extras/testTraceback3.out | 0 .../extras/testTraceback3.py | 0 .../extras/testTypeKeyword_ok.err | 0 .../extras/testTypeKeyword_ok.out | 0 .../extras/testTypeKeyword_ok.py | 0 .../extras/testTypes1.err | 4 +- .../extras/testTypes1.err-notypes | 0 .../extras/testTypes1.out | 0 .../extras/testTypes1.py | 0 .../extras/testTypes2.err | 4 +- .../extras/testTypes2.err-notypes | 0 .../extras/testTypes2.out | 0 .../extras/testTypes2.py | 0 .../extras/testTypes2_ok.py | 0 .../extras/testTypesDict1.err | 0 .../extras/testTypesDict1.out | 0 .../extras/testTypesDict3.err | 6 +- .../extras/testTypesDict3.out | 0 .../extras/testTypesDict3.py | 0 .../extras/testTypesDict4.err | 8 +- .../extras/testTypesDict4.out | 0 .../extras/testTypesDict4.py | 0 .../extras/testTypesHigherOrderFuns.err | 6 +- .../extras/testTypesHigherOrderFuns.out | 0 .../extras/testTypesHigherOrderFuns.py | 0 .../extras/testTypesHigherOrderFuns2_ok.err | 0 .../extras/testTypesHigherOrderFuns2_ok.out | 0 .../extras/testTypesHigherOrderFuns2_ok.py | 0 .../extras/testTypesHigherOrderFuns3.err | 4 +- .../extras/testTypesHigherOrderFuns3.out | 0 .../extras/testTypesHigherOrderFuns3.py | 0 .../extras/testTypesHigherOrderFuns4_ok.err | 0 .../extras/testTypesHigherOrderFuns4_ok.out | 0 .../extras/testTypesHigherOrderFuns4_ok.py | 0 .../extras/testTypesHigherOrderFuns5_ok.err | 0 .../extras/testTypesHigherOrderFuns5_ok.out | 0 .../extras/testTypesHigherOrderFuns5_ok.py | 0 .../extras/testTypesProtos1.err | 6 +- .../extras/testTypesProtos1.out | 0 .../extras/testTypesProtos1.py | 0 .../extras/testTypesProtos2.err | 6 +- .../extras/testTypesProtos2.out | 0 .../extras/testTypesProtos2.py | 0 .../extras/testTypesProtos3.err | 6 +- .../extras/testTypesProtos3.out | 0 .../extras/testTypesProtos3.py | 0 .../extras/testTypesProtos4.err | 8 +- .../extras/testTypesProtos4.out | 0 .../extras/testTypesProtos4.py | 0 .../extras/testTypesProtos5_ok.err | 0 .../extras/testTypesProtos5_ok.out | 0 .../extras/testTypesProtos5_ok.py | 0 .../extras/testTypesProtos6.err | 12 +- .../extras/testTypesProtos6.out | 0 .../extras/testTypesProtos6.py | 0 .../extras/testTypesProtos7.err | 12 +- .../extras/testTypesProtos7.out | 0 .../extras/testTypesProtos7.py | 0 .../extras/testTypesProtos8.err | 6 +- .../extras/testTypesProtos8.out | 0 .../extras/testTypesProtos8.py | 0 .../extras/testTypesProtos9.err | 6 +- .../extras/testTypesProtos9.out | 0 .../extras/testTypesProtos9.py | 0 .../extras/testTypesRecordInheritance.err | 0 .../testTypesRecordInheritance.err-3.10 | 0 .../testTypesRecordInheritance.err-3.11 | 0 .../testTypesRecordInheritance.err-3.12 | 4 +- .../extras/testTypesRecordInheritance.out | 0 .../extras/testTypesRecordInheritance.py | 0 .../extras/testTypesReturn.err | 6 +- .../extras/testTypesReturn.out | 0 .../extras/testTypesReturn.py | 0 .../extras/testTypesSequence1.err | 4 +- .../extras/testTypesSequence1.out | 0 .../extras/testTypesSequence1.py | 0 .../extras/testTypesSequence2.err | 4 +- .../extras/testTypesSequence2.out | 0 .../extras/testTypesSequence2.py | 0 .../extras/testTypesSet1.err | 0 .../extras/testTypesSet1.out | 0 .../extras/testTypesSubclassing1.err | 6 +- .../extras/testTypesSubclassing1.out | 0 .../extras/testTypesSubclassing1.py | 0 .../extras/testTypesTuple1.err | 4 +- .../extras/testTypesTuple1.out | 0 .../extras/testTypesTuple1.py | 0 .../extras/testTypesWrapperEq_ok.err | 0 .../extras/testTypesWrapperEq_ok.out | 0 .../extras/testTypesWrapperEq_ok.py | 0 .../extras/testUnion2_ok.err | 0 .../extras/testUnion2_ok.out | 0 .../extras/testUnion2_ok.py | 0 .../extras/testUnion3_ok.err | 0 .../extras/testUnion3_ok.out | 0 .../extras/testUnion3_ok.py | 0 .../extras/testUnionLiteral_ok.err | 0 .../extras/testUnionLiteral_ok.out | 0 .../extras/testUnionLiteral_ok.py | 0 .../extras/testUnionOfUnion_ok.err | 0 .../extras/testUnionOfUnion_ok.out | 0 .../extras/testUnionOfUnion_ok.py | 0 .../extras/testUnion_ok.err | 0 .../extras/testUnion_ok.out | 0 .../extras/testUnion_ok.py | 0 .../extras/testWrongKeywordArg.err | 4 +- .../extras/testWrongKeywordArg.out | 0 .../extras/testWrongKeywordArg.py | 0 .../extras/testWrongKeywordArg2.err | 0 .../extras/testWrongKeywordArg2.err-3.10 | 0 .../extras/testWrongKeywordArg2.err-3.11 | 0 .../extras/testWrongKeywordArg2.err-3.12 | 4 +- .../extras/testWrongKeywordArg2.out | 0 .../extras/testWrongKeywordArg2.py | 0 .../extras/testWrongKeywordArg3.err | 4 +- .../extras/testWrongKeywordArg3.out | 0 .../extras/testWrongKeywordArg3.py | 0 .../extras/testWrongKeywordArg4.err | 4 +- .../extras/testWrongKeywordArg4.out | 0 .../extras/testWrongKeywordArg4.py | 0 .../extras/testWrongKeywordArg5.err | 4 +- .../extras/testWrongKeywordArg5.out | 0 .../extras/testWrongKeywordArg5.py | 0 .../extras/testWrongNumOfArguments.err | 4 +- .../extras/testWrongNumOfArguments.out | 0 .../extras/testWrongNumOfArguments.py | 0 .../extras/testWrongNumOfArguments2.err | 4 +- .../extras/testWrongNumOfArguments2.out | 0 .../extras/testWrongNumOfArguments2.py | 0 .../extras/wrong-caused-by.err | 4 +- .../extras/wrong-caused-by.out | 0 .../extras/wrong-caused-by.py | 0 .../imports/fileWithBothImports.py | 0 .../imports/fileWithImport.py | 0 .../imports/fileWithoutImport.py | 0 .../imports/localMod.py | 0 .../modules/B/mod.py | 0 python/fileTests-2.0.py | 21 --- python/fileTests.py | 17 +++ .../repl-test-checks.py | 0 .../repl-test-lib.py | 0 .../scope-bug-peter.py | 0 .../student-submission-bad.py | 0 .../student-submission-tests-tyerror.py | 0 .../student-submission-tests.py | 0 .../student-submission-tyerror.py | 0 .../student-submission.py | 0 .../testTypes2.py | 0 .../testTypes3.py | 0 .../testTypesInteractive.py | 0 .../testTypesSet1_ok.py | 0 python/integration-tests/testIntegration.py | 40 +++--- python/test-data-2.0/basics/stack.err | 13 -- python/test-data-2.0/extras/main.py | 4 - python/test-data-2.0/extras/testArgs_ok.out | 1 - python/test-data-2.0/extras/testCheck_ok.out | 5 - .../test-data-2.0/extras/testDeepEqBug_ok.out | 2 - python/test-data-2.0/extras/testFunEq_ok.out | 2 - .../test-data-2.0/extras/testIndexError.err | 6 - python/test-data-2.0/extras/testTraceback.err | 6 - .../test-data-2.0/extras/testTraceback2.err | 4 - python/test-data/fileWithRecursiveTypes.py | 8 -- python/test-data/fileWithStarImport.py | 7 - python/test-data/modules/B/mod.py | 5 - python/test-data/perf.py | 125 ------------------ python/test-data/testCheck2.err | 4 - python/test-data/testCheck2.out | 0 python/test-data/testCheck2.py | 3 - .../test-data/testDisappearingObject_01.err | 0 .../test-data/testDisappearingObject_01.out | 2 - python/test-data/testDisappearingObject_01.py | 75 ----------- .../test-data/testDisappearingObject_02.err | 0 .../test-data/testDisappearingObject_02.out | 0 python/test-data/testDisappearingObject_02.py | 68 ---------- .../test-data/testDisappearingObject_03.err | 0 .../test-data/testDisappearingObject_03.out | 2 - python/test-data/testDisappearingObject_03.py | 76 ----------- python/test-data/testHintParentheses4.py | 9 -- python/test-data/testHintParentheses5.py | 9 -- python/test-data/testHintParentheses6.py | 10 -- python/test-data/testInferReturnType.err | 0 python/test-data/testInferReturnType.out | 0 python/test-data/testInferReturnType.py | 4 - python/test-data/testInferReturnType2.err | 12 -- python/test-data/testInferReturnType2.out | 0 python/test-data/testInferReturnType2.py | 6 - python/test-data/testInferReturnType3.err | 0 python/test-data/testInferReturnType3.out | 0 python/test-data/testInferReturnType3.py | 22 --- python/test-data/testInferReturnType4.err | 28 ---- python/test-data/testInferReturnType4.out | 0 python/test-data/testInferReturnType4.py | 17 --- python/test-data/testNums.err | 12 -- python/test-data/testNums.out | 0 python/test-data/testNums.py | 8 -- python/test-data/testTypesCollections5.err | 16 --- python/test-data/testTypesCollections5.out | 0 python/test-data/testTypesCollections5.py | 11 -- python/test-data/testTypesDict1_ok.py | 8 -- python/test-data/testUnsortableDicts.err | 0 python/test-data/testUnsortableDicts.out | 1 - python/test-data/testUnsortableDicts.py | 8 -- python/test-data/testWrongNumOfArguments3.py | 6 - python/test-data/typeEnums.py | 12 -- python/test-data/typeRecords.py | 17 --- python/test-data/typeUnion.py | 23 ---- 672 files changed, 365 insertions(+), 983 deletions(-) rename python/{test-data-2.0 => file-test-data}/basics/async.err (83%) rename python/{test-data-2.0 => file-test-data}/basics/async.err_en (83%) rename python/{test-data-2.0 => file-test-data}/basics/async.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/async.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/async.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor.err (75%) rename python/{test-data-2.0 => file-test-data}/basics/constructor.err_en (74%) rename python/{test-data-2.0 => file-test-data}/basics/constructor.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructorDataclass_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/constructor_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs.err (75%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs.err_en (74%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/forwardRefs_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg.err (76%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg.err_en (76%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg_ok.err_None (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg_ok.out_None (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionArg_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult.err (67%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult.err_en (66%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2.err (65%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2.err_en (62%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3.err (61%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3.err_en (60%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult3_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionNoResult_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult.err (70%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult.err_en (68%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult2.err (64%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult2.err_en (62%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/functionResult_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator.err (72%) rename python/{test-data-2.0 => file-test-data}/basics/iterator.err_en (71%) rename python/{test-data-2.0 => file-test-data}/basics/iterator.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/iterator_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs.err (76%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs.err_en (75%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs2.err (76%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs2.err_en (75%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/kwargs_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg.err (74%) rename python/{test-data-2.0 => file-test-data}/basics/listArg.err_en (73%) rename python/{test-data-2.0 => file-test-data}/basics/listArg.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/listArg_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult.err (69%) rename python/{test-data-2.0 => file-test-data}/basics/listResult.err_en (68%) rename python/{test-data-2.0 => file-test-data}/basics/listResult.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/listResult_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/method.err (77%) rename python/{test-data-2.0 => file-test-data}/basics/method.err_en (76%) rename python/{test-data-2.0 => file-test-data}/basics/method.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/method.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/method.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/method_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/method_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/method_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable.err (74%) rename python/{test-data-2.0 => file-test-data}/basics/mutable.err_en (73%) rename python/{test-data-2.0 => file-test-data}/basics/mutable.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2.err (76%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2.err_en (75%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/mutable_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/nested.err (76%) rename python/{test-data-2.0 => file-test-data}/basics/nested.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/nested.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/nestedFun.err (68%) rename python/{test-data-2.0 => file-test-data}/basics/nestedFun.err_en (67%) rename python/{test-data-2.0 => file-test-data}/basics/nestedFun.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/nestedFun.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/nestedFun.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/nosig_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/nosig_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/nosig_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs.err (70%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs.err_en (70%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs2.err (62%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs2.err_en (60%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs3.err (64%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs3.err_en (62%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs3.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs3.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs3.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs4.err (64%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs4.err_en (63%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs4.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs4.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs4.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalArgs_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr.err (68%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr.err_en (68%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr2.err (66%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr2.err_en (64%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr2.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr3.err (67%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr3.err_en (65%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr3.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr3.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr3.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/optionalAttr_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/partial.err (68%) rename python/{test-data-2.0 => file-test-data}/basics/partial.err_en (68%) rename python/{test-data-2.0 => file-test-data}/basics/partial.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/partial.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/partial.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/record.err (78%) rename python/{test-data-2.0 => file-test-data}/basics/record.err_en (77%) rename python/{test-data-2.0 => file-test-data}/basics/record.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/record.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/record.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/record_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/record_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/record_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/sample_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/sample_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/sample_ok.py (100%) create mode 100644 python/file-test-data/basics/stack.err rename python/{test-data-2.0 => file-test-data}/basics/stack.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/stack.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod.err (74%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod.err_en (73%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/staticmethod_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/testCallable.err (75%) rename python/{test-data-2.0 => file-test-data}/basics/testCallable.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/testCallable.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewArgs.err (62%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewArgs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewArgs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewAttrs.err (65%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewAttrs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooFewAttrs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyArgs.err (63%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyArgs.err_en (61%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyArgs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyArgs.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyArgs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyAttrs.err (66%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyAttrs.err_en (64%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyAttrs.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyAttrs.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/tooManyAttrs.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias.err (73%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias.err_en (72%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias.py (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias2.err (71%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias2.err_en (70%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias2.out (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias2.out_en (100%) rename python/{test-data-2.0 => file-test-data}/basics/typeAlias2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/admin_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/admin_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/admin_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.err-3.12 (79%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/declared-at-missing.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidRecord.err (93%) rename python/{test-data-2.0 => file-test-data}/extras/invalidRecord.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidRecord.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType2.err (62%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType3.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType4.err (64%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType4.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/invalidType4.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/main.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/main.out (100%) create mode 100644 python/file-test-data/extras/main.py rename python/{test-data-2.0 => file-test-data}/extras/printModuleNameImport_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/printModuleNameImport_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/printModuleNameImport_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/printModuleName_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/printModuleName_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/printModuleName_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABCMeta.err (70%) rename python/{test-data-2.0 => file-test-data}/extras/testABCMeta.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABCMeta.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABCMeta.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABCMeta.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABC_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABC_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testABC_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testArgs_ok.err (100%) create mode 100644 python/file-test-data/extras/testArgs_ok.out rename python/{test-data-2.0 => file-test-data}/extras/testArgs_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testBugSliceIndices_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testBugSliceIndices_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testBugSliceIndices_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCheckFail_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCheckFail_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCheckFail_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCheck_ok.err (100%) create mode 100644 python/file-test-data/extras/testCheck_ok.out rename python/{test-data-2.0 => file-test-data}/extras/testCheck_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassHierarchy_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassHierarchy_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassHierarchy_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassRecursion_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassRecursion_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testClassRecursion_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testComplex_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testComplex_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testComplex_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testConcat_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testConcat_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testConcat_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCopy_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCopy_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testCopy_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDeepEqBug_ok.err (100%) create mode 100644 python/file-test-data/extras/testDeepEqBug_ok.out rename python/{test-data-2.0 => file-test-data}/extras/testDeepEqBug_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDict_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDict_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDict_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDoubleWrappingDicts_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDoubleWrappingDicts_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testDoubleWrappingDicts_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef1_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef1_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef1_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef2.err (59%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef3_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef3_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef3_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.err-3.12 (56%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef4.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.err-3.12 (77%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef5.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef6_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef6_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef6_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardRef_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testForwardTypeInRecord_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testFunEq_ok.err (100%) create mode 100644 python/file-test-data/extras/testFunEq_ok.out rename python/{test-data-2.0 => file-test-data}/extras/testFunEq_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testGetSource.err (81%) rename python/{test-data-2.0 => file-test-data}/extras/testGetSource.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testGetSource.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses1.err (62%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses2.err (60%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses3.err (62%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHintParentheses3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHof_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHof_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testHof_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testImpossible.err (72%) rename python/{test-data-2.0 => file-test-data}/extras/testImpossible.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testImpossible.py (100%) create mode 100644 python/file-test-data/extras/testIndexError.err rename python/{test-data-2.0 => file-test-data}/extras/testIndexError.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIndexError.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIndexSeq_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIndexSeq_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIndexSeq_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testInvalidLiteral.err (60%) rename python/{test-data-2.0 => file-test-data}/extras/testInvalidLiteral.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testInvalidLiteral.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable1_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable1_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable1_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable3_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable3_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable3_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable4_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable4_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable4_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable5_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable5_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable5_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable6_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable6_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable6_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable7_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable7_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterable7_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterableImplicitAny.err (72%) rename python/{test-data-2.0 => file-test-data}/extras/testIterableImplicitAny.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterableImplicitAny.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator3_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator3_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator3_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator4_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator4_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator4_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testIterator_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteral1.err (79%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteral1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteral1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteralInstanceOf_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteralInstanceOf_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLiteralInstanceOf_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory.err (74%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory2.err (73%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testLockFactory_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testMissingReturn.err (68%) rename python/{test-data-2.0 => file-test-data}/extras/testMissingReturn.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testMissingReturn.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testNameErrorBug_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testNameErrorBug_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testNameErrorBug_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testOriginalTypeNames_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testOriginalTypeNames_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testOriginalTypeNames_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.err-3.12 (60%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypeForwardRef.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.err-3.12 (64%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordSetTypes.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordTypes.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordTypes.err-3.12 (74%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordTypes.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testRecordTypes.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTodo.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/testTodo.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTodo.py (100%) create mode 100644 python/file-test-data/extras/testTraceback.err rename python/{test-data-2.0 => file-test-data}/extras/testTraceback.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback.py (100%) create mode 100644 python/file-test-data/extras/testTraceback2.err rename python/{test-data-2.0 => file-test-data}/extras/testTraceback2.err-3.10.0 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback2.err-3.9 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback3.err (57%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTraceback3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypeKeyword_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypeKeyword_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypeKeyword_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes1.err (73%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes1.err-notypes (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes2.err (73%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes2.err-notypes (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypes2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict1.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict3.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict4.err (64%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict4.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesDict4.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns.err (68%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns3.err (78%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns4_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns4_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns4_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns5_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns5_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesHigherOrderFuns5_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos1.err (68%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos2.err (68%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos3.err (69%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos4.err (65%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos4.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos4.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos5_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos5_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos5_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos6.err (56%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos6.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos6.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos7.err (55%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos7.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos7.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos8.err (67%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos8.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos8.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos9.err (67%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos9.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesProtos9.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.err-3.12 (70%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesRecordInheritance.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesReturn.err (70%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesReturn.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesReturn.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence1.err (72%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence2.err (74%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSequence2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSet1.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSet1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSubclassing1.err (68%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSubclassing1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesSubclassing1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesTuple1.err (72%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesTuple1.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesTuple1.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesWrapperEq_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesWrapperEq_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testTypesWrapperEq_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion2_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion2_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion2_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion3_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion3_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion3_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionLiteral_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionLiteral_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionLiteral_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionOfUnion_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionOfUnion_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnionOfUnion_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion_ok.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion_ok.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testUnion_ok.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg.err (61%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.err (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.err-3.10 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.err-3.11 (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.err-3.12 (67%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg3.err (67%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg3.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg3.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg4.err (62%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg4.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg4.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg5.err (58%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg5.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongKeywordArg5.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments.err (57%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments2.err (59%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments2.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/testWrongNumOfArguments2.py (100%) rename python/{test-data-2.0 => file-test-data}/extras/wrong-caused-by.err (79%) rename python/{test-data-2.0 => file-test-data}/extras/wrong-caused-by.out (100%) rename python/{test-data-2.0 => file-test-data}/extras/wrong-caused-by.py (100%) rename python/{test-data-2.0 => file-test-data}/imports/fileWithBothImports.py (100%) rename python/{test-data-2.0 => file-test-data}/imports/fileWithImport.py (100%) rename python/{test-data-2.0 => file-test-data}/imports/fileWithoutImport.py (100%) rename python/{test-data-2.0 => file-test-data}/imports/localMod.py (100%) rename python/{test-data-2.0 => file-test-data}/modules/B/mod.py (100%) delete mode 100644 python/fileTests-2.0.py rename python/{test-data => integration-test-data}/repl-test-checks.py (100%) rename python/{test-data => integration-test-data}/repl-test-lib.py (100%) rename python/{test-data => integration-test-data}/scope-bug-peter.py (100%) rename python/{test-data => integration-test-data}/student-submission-bad.py (100%) rename python/{test-data => integration-test-data}/student-submission-tests-tyerror.py (100%) rename python/{test-data => integration-test-data}/student-submission-tests.py (100%) rename python/{test-data => integration-test-data}/student-submission-tyerror.py (100%) rename python/{test-data => integration-test-data}/student-submission.py (100%) rename python/{test-data => integration-test-data}/testTypes2.py (100%) rename python/{test-data => integration-test-data}/testTypes3.py (100%) rename python/{test-data => integration-test-data}/testTypesInteractive.py (100%) rename python/{test-data => integration-test-data}/testTypesSet1_ok.py (100%) delete mode 100644 python/test-data-2.0/basics/stack.err delete mode 100644 python/test-data-2.0/extras/main.py delete mode 100644 python/test-data-2.0/extras/testArgs_ok.out delete mode 100644 python/test-data-2.0/extras/testCheck_ok.out delete mode 100644 python/test-data-2.0/extras/testDeepEqBug_ok.out delete mode 100644 python/test-data-2.0/extras/testFunEq_ok.out delete mode 100644 python/test-data-2.0/extras/testIndexError.err delete mode 100644 python/test-data-2.0/extras/testTraceback.err delete mode 100644 python/test-data-2.0/extras/testTraceback2.err delete mode 100644 python/test-data/fileWithRecursiveTypes.py delete mode 100644 python/test-data/fileWithStarImport.py delete mode 100644 python/test-data/modules/B/mod.py delete mode 100644 python/test-data/perf.py delete mode 100644 python/test-data/testCheck2.err delete mode 100644 python/test-data/testCheck2.out delete mode 100644 python/test-data/testCheck2.py delete mode 100644 python/test-data/testDisappearingObject_01.err delete mode 100644 python/test-data/testDisappearingObject_01.out delete mode 100644 python/test-data/testDisappearingObject_01.py delete mode 100644 python/test-data/testDisappearingObject_02.err delete mode 100644 python/test-data/testDisappearingObject_02.out delete mode 100644 python/test-data/testDisappearingObject_02.py delete mode 100644 python/test-data/testDisappearingObject_03.err delete mode 100644 python/test-data/testDisappearingObject_03.out delete mode 100644 python/test-data/testDisappearingObject_03.py delete mode 100644 python/test-data/testHintParentheses4.py delete mode 100644 python/test-data/testHintParentheses5.py delete mode 100644 python/test-data/testHintParentheses6.py delete mode 100644 python/test-data/testInferReturnType.err delete mode 100644 python/test-data/testInferReturnType.out delete mode 100644 python/test-data/testInferReturnType.py delete mode 100644 python/test-data/testInferReturnType2.err delete mode 100644 python/test-data/testInferReturnType2.out delete mode 100644 python/test-data/testInferReturnType2.py delete mode 100644 python/test-data/testInferReturnType3.err delete mode 100644 python/test-data/testInferReturnType3.out delete mode 100644 python/test-data/testInferReturnType3.py delete mode 100644 python/test-data/testInferReturnType4.err delete mode 100644 python/test-data/testInferReturnType4.out delete mode 100644 python/test-data/testInferReturnType4.py delete mode 100644 python/test-data/testNums.err delete mode 100644 python/test-data/testNums.out delete mode 100644 python/test-data/testNums.py delete mode 100644 python/test-data/testTypesCollections5.err delete mode 100644 python/test-data/testTypesCollections5.out delete mode 100644 python/test-data/testTypesCollections5.py delete mode 100644 python/test-data/testTypesDict1_ok.py delete mode 100644 python/test-data/testUnsortableDicts.err delete mode 100644 python/test-data/testUnsortableDicts.out delete mode 100644 python/test-data/testUnsortableDicts.py delete mode 100644 python/test-data/testWrongNumOfArguments3.py delete mode 100644 python/test-data/typeEnums.py delete mode 100644 python/test-data/typeRecords.py delete mode 100644 python/test-data/typeUnion.py diff --git a/python/test-data-2.0/basics/async.err b/python/file-test-data/basics/async.err similarity index 83% rename from python/test-data-2.0/basics/async.err rename to python/file-test-data/basics/async.err index 1feb6b7f..c9c465a6 100644 --- a/python/test-data-2.0/basics/async.err +++ b/python/file-test-data/basics/async.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/async.py", line 10, in + File "file-test-data/basics/async.py", line 10, in asyncio.run(main()) File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run return runner.run(main) @@ -7,7 +7,7 @@ Traceback (most recent call last): signal.signal(signal.SIGINT, signal.default_int_handler) File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete return future.result() - File "test-data-2.0/basics/async.py", line 7, in main + File "file-test-data/basics/async.py", line 7, in main x = await foo("blub") WyppTypeError: "blub" @@ -15,7 +15,7 @@ WyppTypeError: "blub" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/basics/async.py +## Datei file-test-data/basics/async.py ## Fehlerhafter Aufruf in Zeile 7: x = await foo("blub") diff --git a/python/test-data-2.0/basics/async.err_en b/python/file-test-data/basics/async.err_en similarity index 83% rename from python/test-data-2.0/basics/async.err_en rename to python/file-test-data/basics/async.err_en index 62be50a7..5ccb4ac0 100644 --- a/python/test-data-2.0/basics/async.err_en +++ b/python/file-test-data/basics/async.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/async.py", line 10, in + File "file-test-data/basics/async.py", line 10, in asyncio.run(main()) File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run return runner.run(main) @@ -7,7 +7,7 @@ Traceback (most recent call last): signal.signal(signal.SIGINT, signal.default_int_handler) File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete return future.result() - File "test-data-2.0/basics/async.py", line 7, in main + File "file-test-data/basics/async.py", line 7, in main x = await foo("blub") WyppTypeError: "blub" @@ -15,7 +15,7 @@ WyppTypeError: "blub" The call of function `foo` expects value of type `int` as 1st argument. But the value given has type `str`. -## File test-data-2.0/basics/async.py +## File file-test-data/basics/async.py ## Problematic call in line 7: x = await foo("blub") diff --git a/python/test-data-2.0/basics/async.out b/python/file-test-data/basics/async.out similarity index 100% rename from python/test-data-2.0/basics/async.out rename to python/file-test-data/basics/async.out diff --git a/python/test-data-2.0/basics/async.out_en b/python/file-test-data/basics/async.out_en similarity index 100% rename from python/test-data-2.0/basics/async.out_en rename to python/file-test-data/basics/async.out_en diff --git a/python/test-data-2.0/basics/async.py b/python/file-test-data/basics/async.py similarity index 100% rename from python/test-data-2.0/basics/async.py rename to python/file-test-data/basics/async.py diff --git a/python/test-data-2.0/basics/constructor.err b/python/file-test-data/basics/constructor.err similarity index 75% rename from python/test-data-2.0/basics/constructor.err rename to python/file-test-data/basics/constructor.err index 41684388..9d8fb5ad 100644 --- a/python/test-data-2.0/basics/constructor.err +++ b/python/file-test-data/basics/constructor.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/constructor.py", line 5, in + File "file-test-data/basics/constructor.py", line 5, in c = C("1") WyppTypeError: "1" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/constructor.py +## Datei file-test-data/basics/constructor.py ## Fehlerhafter Aufruf in Zeile 5: c = C("1") diff --git a/python/test-data-2.0/basics/constructor.err_en b/python/file-test-data/basics/constructor.err_en similarity index 74% rename from python/test-data-2.0/basics/constructor.err_en rename to python/file-test-data/basics/constructor.err_en index defa90af..44f7d2ce 100644 --- a/python/test-data-2.0/basics/constructor.err_en +++ b/python/file-test-data/basics/constructor.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/constructor.py", line 5, in + File "file-test-data/basics/constructor.py", line 5, in c = C("1") WyppTypeError: "1" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/constructor.py +## File file-test-data/basics/constructor.py ## Problematic call in line 5: c = C("1") diff --git a/python/test-data-2.0/basics/constructor.out b/python/file-test-data/basics/constructor.out similarity index 100% rename from python/test-data-2.0/basics/constructor.out rename to python/file-test-data/basics/constructor.out diff --git a/python/test-data-2.0/basics/constructor.out_en b/python/file-test-data/basics/constructor.out_en similarity index 100% rename from python/test-data-2.0/basics/constructor.out_en rename to python/file-test-data/basics/constructor.out_en diff --git a/python/test-data-2.0/basics/constructor.py b/python/file-test-data/basics/constructor.py similarity index 100% rename from python/test-data-2.0/basics/constructor.py rename to python/file-test-data/basics/constructor.py diff --git a/python/test-data-2.0/basics/constructorDataclass_ok.py b/python/file-test-data/basics/constructorDataclass_ok.py similarity index 100% rename from python/test-data-2.0/basics/constructorDataclass_ok.py rename to python/file-test-data/basics/constructorDataclass_ok.py diff --git a/python/test-data-2.0/basics/constructor_ok.err b/python/file-test-data/basics/constructor_ok.err similarity index 100% rename from python/test-data-2.0/basics/constructor_ok.err rename to python/file-test-data/basics/constructor_ok.err diff --git a/python/test-data-2.0/basics/constructor_ok.out b/python/file-test-data/basics/constructor_ok.out similarity index 100% rename from python/test-data-2.0/basics/constructor_ok.out rename to python/file-test-data/basics/constructor_ok.out diff --git a/python/test-data-2.0/basics/constructor_ok.py b/python/file-test-data/basics/constructor_ok.py similarity index 100% rename from python/test-data-2.0/basics/constructor_ok.py rename to python/file-test-data/basics/constructor_ok.py diff --git a/python/test-data-2.0/basics/forwardRefs.err b/python/file-test-data/basics/forwardRefs.err similarity index 75% rename from python/test-data-2.0/basics/forwardRefs.err rename to python/file-test-data/basics/forwardRefs.err index 20150132..e98cc51d 100644 --- a/python/test-data-2.0/basics/forwardRefs.err +++ b/python/file-test-data/basics/forwardRefs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/forwardRefs.py", line 12, in + File "file-test-data/basics/forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/forwardRefs.py +## Datei file-test-data/basics/forwardRefs.py ## Fehlerhafter Aufruf in Zeile 12: a.foo(a) diff --git a/python/test-data-2.0/basics/forwardRefs.err_en b/python/file-test-data/basics/forwardRefs.err_en similarity index 74% rename from python/test-data-2.0/basics/forwardRefs.err_en rename to python/file-test-data/basics/forwardRefs.err_en index 5172c0dd..20d22f20 100644 --- a/python/test-data-2.0/basics/forwardRefs.err_en +++ b/python/file-test-data/basics/forwardRefs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/forwardRefs.py", line 12, in + File "file-test-data/basics/forwardRefs.py", line 12, in a.foo(a) WyppTypeError: <__wypp__.A object at 0x00> @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/forwardRefs.py +## File file-test-data/basics/forwardRefs.py ## Problematic call in line 12: a.foo(a) diff --git a/python/test-data-2.0/basics/forwardRefs.out b/python/file-test-data/basics/forwardRefs.out similarity index 100% rename from python/test-data-2.0/basics/forwardRefs.out rename to python/file-test-data/basics/forwardRefs.out diff --git a/python/test-data-2.0/basics/forwardRefs.out_en b/python/file-test-data/basics/forwardRefs.out_en similarity index 100% rename from python/test-data-2.0/basics/forwardRefs.out_en rename to python/file-test-data/basics/forwardRefs.out_en diff --git a/python/test-data-2.0/basics/forwardRefs.py b/python/file-test-data/basics/forwardRefs.py similarity index 100% rename from python/test-data-2.0/basics/forwardRefs.py rename to python/file-test-data/basics/forwardRefs.py diff --git a/python/test-data-2.0/basics/forwardRefs_ok.err b/python/file-test-data/basics/forwardRefs_ok.err similarity index 100% rename from python/test-data-2.0/basics/forwardRefs_ok.err rename to python/file-test-data/basics/forwardRefs_ok.err diff --git a/python/test-data-2.0/basics/forwardRefs_ok.out b/python/file-test-data/basics/forwardRefs_ok.out similarity index 100% rename from python/test-data-2.0/basics/forwardRefs_ok.out rename to python/file-test-data/basics/forwardRefs_ok.out diff --git a/python/test-data-2.0/basics/forwardRefs_ok.py b/python/file-test-data/basics/forwardRefs_ok.py similarity index 100% rename from python/test-data-2.0/basics/forwardRefs_ok.py rename to python/file-test-data/basics/forwardRefs_ok.py diff --git a/python/test-data-2.0/basics/functionArg.err b/python/file-test-data/basics/functionArg.err similarity index 76% rename from python/test-data-2.0/basics/functionArg.err rename to python/file-test-data/basics/functionArg.err index 960c3a68..003a56f3 100644 --- a/python/test-data-2.0/basics/functionArg.err +++ b/python/file-test-data/basics/functionArg.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionArg.py", line 8, in + File "file-test-data/basics/functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" @@ -7,7 +7,7 @@ WyppTypeError: "1" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als zweites Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/basics/functionArg.py +## Datei file-test-data/basics/functionArg.py ## Fehlerhafter Aufruf in Zeile 8: print(foo(foo(1, "1"), 42)) diff --git a/python/test-data-2.0/basics/functionArg.err_en b/python/file-test-data/basics/functionArg.err_en similarity index 76% rename from python/test-data-2.0/basics/functionArg.err_en rename to python/file-test-data/basics/functionArg.err_en index f05a410e..8f4994f8 100644 --- a/python/test-data-2.0/basics/functionArg.err_en +++ b/python/file-test-data/basics/functionArg.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionArg.py", line 8, in + File "file-test-data/basics/functionArg.py", line 8, in print(foo(foo(1, "1"), 42)) WyppTypeError: "1" @@ -7,7 +7,7 @@ WyppTypeError: "1" The call of function `foo` expects value of type `int` as 2nd argument. But the value given has type `str`. -## File test-data-2.0/basics/functionArg.py +## File file-test-data/basics/functionArg.py ## Problematic call in line 8: print(foo(foo(1, "1"), 42)) diff --git a/python/test-data-2.0/basics/functionArg.out b/python/file-test-data/basics/functionArg.out similarity index 100% rename from python/test-data-2.0/basics/functionArg.out rename to python/file-test-data/basics/functionArg.out diff --git a/python/test-data-2.0/basics/functionArg.out_en b/python/file-test-data/basics/functionArg.out_en similarity index 100% rename from python/test-data-2.0/basics/functionArg.out_en rename to python/file-test-data/basics/functionArg.out_en diff --git a/python/test-data-2.0/basics/functionArg.py b/python/file-test-data/basics/functionArg.py similarity index 100% rename from python/test-data-2.0/basics/functionArg.py rename to python/file-test-data/basics/functionArg.py diff --git a/python/test-data-2.0/basics/functionArg_ok.err b/python/file-test-data/basics/functionArg_ok.err similarity index 100% rename from python/test-data-2.0/basics/functionArg_ok.err rename to python/file-test-data/basics/functionArg_ok.err diff --git a/python/test-data-2.0/basics/functionArg_ok.err_None b/python/file-test-data/basics/functionArg_ok.err_None similarity index 100% rename from python/test-data-2.0/basics/functionArg_ok.err_None rename to python/file-test-data/basics/functionArg_ok.err_None diff --git a/python/test-data-2.0/basics/functionArg_ok.out b/python/file-test-data/basics/functionArg_ok.out similarity index 100% rename from python/test-data-2.0/basics/functionArg_ok.out rename to python/file-test-data/basics/functionArg_ok.out diff --git a/python/test-data-2.0/basics/functionArg_ok.out_None b/python/file-test-data/basics/functionArg_ok.out_None similarity index 100% rename from python/test-data-2.0/basics/functionArg_ok.out_None rename to python/file-test-data/basics/functionArg_ok.out_None diff --git a/python/test-data-2.0/basics/functionArg_ok.py b/python/file-test-data/basics/functionArg_ok.py similarity index 100% rename from python/test-data-2.0/basics/functionArg_ok.py rename to python/file-test-data/basics/functionArg_ok.py diff --git a/python/test-data-2.0/basics/functionNoResult.err b/python/file-test-data/basics/functionNoResult.err similarity index 67% rename from python/test-data-2.0/basics/functionNoResult.err rename to python/file-test-data/basics/functionNoResult.err index 108d26f4..11e69b2c 100644 --- a/python/test-data-2.0/basics/functionNoResult.err +++ b/python/file-test-data/basics/functionNoResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult.py", line 4, in + File "file-test-data/basics/functionNoResult.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult.py", line 2, in foo + File "file-test-data/basics/functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei test-data-2.0/basics/functionNoResult.py +## Datei file-test-data/basics/functionNoResult.py ## Rückgabetyp deklariert in Zeile 1: def foo(i: int) -> None: diff --git a/python/test-data-2.0/basics/functionNoResult.err_en b/python/file-test-data/basics/functionNoResult.err_en similarity index 66% rename from python/test-data-2.0/basics/functionNoResult.err_en rename to python/file-test-data/basics/functionNoResult.err_en index 01ad43c9..304cff85 100644 --- a/python/test-data-2.0/basics/functionNoResult.err_en +++ b/python/file-test-data/basics/functionNoResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult.py", line 4, in + File "file-test-data/basics/functionNoResult.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult.py", line 2, in foo + File "file-test-data/basics/functionNoResult.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File test-data-2.0/basics/functionNoResult.py +## File file-test-data/basics/functionNoResult.py ## Result type declared in line 1: def foo(i: int) -> None: diff --git a/python/test-data-2.0/basics/functionNoResult.out b/python/file-test-data/basics/functionNoResult.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult.out rename to python/file-test-data/basics/functionNoResult.out diff --git a/python/test-data-2.0/basics/functionNoResult.out_en b/python/file-test-data/basics/functionNoResult.out_en similarity index 100% rename from python/test-data-2.0/basics/functionNoResult.out_en rename to python/file-test-data/basics/functionNoResult.out_en diff --git a/python/test-data-2.0/basics/functionNoResult.py b/python/file-test-data/basics/functionNoResult.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult.py rename to python/file-test-data/basics/functionNoResult.py diff --git a/python/test-data-2.0/basics/functionNoResult2.err b/python/file-test-data/basics/functionNoResult2.err similarity index 65% rename from python/test-data-2.0/basics/functionNoResult2.err rename to python/file-test-data/basics/functionNoResult2.err index 1f225026..c890beaa 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err +++ b/python/file-test-data/basics/functionNoResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult2.py", line 4, in + File "file-test-data/basics/functionNoResult2.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult2.py", line 2, in foo + File "file-test-data/basics/functionNoResult2.py", line 2, in foo pass WyppTypeError: kein Rückgabewert vorhanden @@ -9,7 +9,7 @@ WyppTypeError: kein Rückgabewert vorhanden Rückgabewert vom Typ `int` erwartet bei Aufruf der Funktion `foo`. Aber kein Rückgabewert vorhanden. -## Datei test-data-2.0/basics/functionNoResult2.py +## Datei file-test-data/basics/functionNoResult2.py ## Rückgabetyp deklariert in Zeile 1: def foo(i: int) -> int: diff --git a/python/test-data-2.0/basics/functionNoResult2.err_en b/python/file-test-data/basics/functionNoResult2.err_en similarity index 62% rename from python/test-data-2.0/basics/functionNoResult2.err_en rename to python/file-test-data/basics/functionNoResult2.err_en index 5419d75c..627f5d4c 100644 --- a/python/test-data-2.0/basics/functionNoResult2.err_en +++ b/python/file-test-data/basics/functionNoResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult2.py", line 4, in + File "file-test-data/basics/functionNoResult2.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult2.py", line 2, in foo + File "file-test-data/basics/functionNoResult2.py", line 2, in foo pass WyppTypeError: no result returned @@ -9,7 +9,7 @@ WyppTypeError: no result returned Expecting return value of type `int` when calling function `foo`. But no value returned. -## File test-data-2.0/basics/functionNoResult2.py +## File file-test-data/basics/functionNoResult2.py ## Result type declared in line 1: def foo(i: int) -> int: diff --git a/python/test-data-2.0/basics/functionNoResult2.out b/python/file-test-data/basics/functionNoResult2.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2.out rename to python/file-test-data/basics/functionNoResult2.out diff --git a/python/test-data-2.0/basics/functionNoResult2.out_en b/python/file-test-data/basics/functionNoResult2.out_en similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2.out_en rename to python/file-test-data/basics/functionNoResult2.out_en diff --git a/python/test-data-2.0/basics/functionNoResult2.py b/python/file-test-data/basics/functionNoResult2.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2.py rename to python/file-test-data/basics/functionNoResult2.py diff --git a/python/test-data-2.0/basics/functionNoResult2_ok.err b/python/file-test-data/basics/functionNoResult2_ok.err similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2_ok.err rename to python/file-test-data/basics/functionNoResult2_ok.err diff --git a/python/test-data-2.0/basics/functionNoResult2_ok.out b/python/file-test-data/basics/functionNoResult2_ok.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2_ok.out rename to python/file-test-data/basics/functionNoResult2_ok.out diff --git a/python/test-data-2.0/basics/functionNoResult2_ok.py b/python/file-test-data/basics/functionNoResult2_ok.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult2_ok.py rename to python/file-test-data/basics/functionNoResult2_ok.py diff --git a/python/test-data-2.0/basics/functionNoResult3.err b/python/file-test-data/basics/functionNoResult3.err similarity index 61% rename from python/test-data-2.0/basics/functionNoResult3.err rename to python/file-test-data/basics/functionNoResult3.err index 6b8a331c..887347a5 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err +++ b/python/file-test-data/basics/functionNoResult3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult3.py", line 4, in + File "file-test-data/basics/functionNoResult3.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult3.py", line 2, in foo + File "file-test-data/basics/functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei test-data-2.0/basics/functionNoResult3.py +## Datei file-test-data/basics/functionNoResult3.py ## Fehlerhaftes return in Zeile 2: return "x" diff --git a/python/test-data-2.0/basics/functionNoResult3.err_en b/python/file-test-data/basics/functionNoResult3.err_en similarity index 60% rename from python/test-data-2.0/basics/functionNoResult3.err_en rename to python/file-test-data/basics/functionNoResult3.err_en index 84374e36..d4f2bad8 100644 --- a/python/test-data-2.0/basics/functionNoResult3.err_en +++ b/python/file-test-data/basics/functionNoResult3.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionNoResult3.py", line 4, in + File "file-test-data/basics/functionNoResult3.py", line 4, in foo(1) - File "test-data-2.0/basics/functionNoResult3.py", line 2, in foo + File "file-test-data/basics/functionNoResult3.py", line 2, in foo return "x" WyppTypeError: "x" @@ -9,7 +9,7 @@ WyppTypeError: "x" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File test-data-2.0/basics/functionNoResult3.py +## File file-test-data/basics/functionNoResult3.py ## Problematic return in line 2: return "x" diff --git a/python/test-data-2.0/basics/functionNoResult3.out b/python/file-test-data/basics/functionNoResult3.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3.out rename to python/file-test-data/basics/functionNoResult3.out diff --git a/python/test-data-2.0/basics/functionNoResult3.out_en b/python/file-test-data/basics/functionNoResult3.out_en similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3.out_en rename to python/file-test-data/basics/functionNoResult3.out_en diff --git a/python/test-data-2.0/basics/functionNoResult3.py b/python/file-test-data/basics/functionNoResult3.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3.py rename to python/file-test-data/basics/functionNoResult3.py diff --git a/python/test-data-2.0/basics/functionNoResult3_ok.err b/python/file-test-data/basics/functionNoResult3_ok.err similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3_ok.err rename to python/file-test-data/basics/functionNoResult3_ok.err diff --git a/python/test-data-2.0/basics/functionNoResult3_ok.out b/python/file-test-data/basics/functionNoResult3_ok.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3_ok.out rename to python/file-test-data/basics/functionNoResult3_ok.out diff --git a/python/test-data-2.0/basics/functionNoResult3_ok.py b/python/file-test-data/basics/functionNoResult3_ok.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult3_ok.py rename to python/file-test-data/basics/functionNoResult3_ok.py diff --git a/python/test-data-2.0/basics/functionNoResult_ok.err b/python/file-test-data/basics/functionNoResult_ok.err similarity index 100% rename from python/test-data-2.0/basics/functionNoResult_ok.err rename to python/file-test-data/basics/functionNoResult_ok.err diff --git a/python/test-data-2.0/basics/functionNoResult_ok.out b/python/file-test-data/basics/functionNoResult_ok.out similarity index 100% rename from python/test-data-2.0/basics/functionNoResult_ok.out rename to python/file-test-data/basics/functionNoResult_ok.out diff --git a/python/test-data-2.0/basics/functionNoResult_ok.py b/python/file-test-data/basics/functionNoResult_ok.py similarity index 100% rename from python/test-data-2.0/basics/functionNoResult_ok.py rename to python/file-test-data/basics/functionNoResult_ok.py diff --git a/python/test-data-2.0/basics/functionResult.err b/python/file-test-data/basics/functionResult.err similarity index 70% rename from python/test-data-2.0/basics/functionResult.err rename to python/file-test-data/basics/functionResult.err index 4715f728..fccc0a10 100644 --- a/python/test-data-2.0/basics/functionResult.err +++ b/python/file-test-data/basics/functionResult.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionResult.py", line 7, in + File "file-test-data/basics/functionResult.py", line 7, in foo(1) - File "test-data-2.0/basics/functionResult.py", line 5, in foo + File "file-test-data/basics/functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ 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 test-data-2.0/basics/functionResult.py +## Datei file-test-data/basics/functionResult.py ## Rückgabetyp deklariert in Zeile 4: def foo(i: int) -> int: diff --git a/python/test-data-2.0/basics/functionResult.err_en b/python/file-test-data/basics/functionResult.err_en similarity index 68% rename from python/test-data-2.0/basics/functionResult.err_en rename to python/file-test-data/basics/functionResult.err_en index 5ae4c3d7..56189aa0 100644 --- a/python/test-data-2.0/basics/functionResult.err_en +++ b/python/file-test-data/basics/functionResult.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionResult.py", line 7, in + File "file-test-data/basics/functionResult.py", line 7, in foo(1) - File "test-data-2.0/basics/functionResult.py", line 5, in foo + File "file-test-data/basics/functionResult.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ WyppTypeError: "foo_1" Expecting return value of type `int` when calling function `foo`. But the call returns a value of type `str`. -## File test-data-2.0/basics/functionResult.py +## File file-test-data/basics/functionResult.py ## Result type declared in line 4: def foo(i: int) -> int: diff --git a/python/test-data-2.0/basics/functionResult.out b/python/file-test-data/basics/functionResult.out similarity index 100% rename from python/test-data-2.0/basics/functionResult.out rename to python/file-test-data/basics/functionResult.out diff --git a/python/test-data-2.0/basics/functionResult.out_en b/python/file-test-data/basics/functionResult.out_en similarity index 100% rename from python/test-data-2.0/basics/functionResult.out_en rename to python/file-test-data/basics/functionResult.out_en diff --git a/python/test-data-2.0/basics/functionResult.py b/python/file-test-data/basics/functionResult.py similarity index 100% rename from python/test-data-2.0/basics/functionResult.py rename to python/file-test-data/basics/functionResult.py diff --git a/python/test-data-2.0/basics/functionResult2.err b/python/file-test-data/basics/functionResult2.err similarity index 64% rename from python/test-data-2.0/basics/functionResult2.err rename to python/file-test-data/basics/functionResult2.err index 9d3cc233..ab040019 100644 --- a/python/test-data-2.0/basics/functionResult2.err +++ b/python/file-test-data/basics/functionResult2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionResult2.py", line 7, in + File "file-test-data/basics/functionResult2.py", line 7, in foo(1) - File "test-data-2.0/basics/functionResult2.py", line 5, in foo + File "file-test-data/basics/functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ WyppTypeError: "foo_1" Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`. Aber der Aufruf gibt einen Wert vom Typ `str` zurück. -## Datei test-data-2.0/basics/functionResult2.py +## Datei file-test-data/basics/functionResult2.py ## Fehlerhaftes return in Zeile 5: return "foo_" + str(i) diff --git a/python/test-data-2.0/basics/functionResult2.err_en b/python/file-test-data/basics/functionResult2.err_en similarity index 62% rename from python/test-data-2.0/basics/functionResult2.err_en rename to python/file-test-data/basics/functionResult2.err_en index d96d8fa8..1d4c4f31 100644 --- a/python/test-data-2.0/basics/functionResult2.err_en +++ b/python/file-test-data/basics/functionResult2.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/functionResult2.py", line 7, in + File "file-test-data/basics/functionResult2.py", line 7, in foo(1) - File "test-data-2.0/basics/functionResult2.py", line 5, in foo + File "file-test-data/basics/functionResult2.py", line 5, in foo return "foo_" + str(i) WyppTypeError: "foo_1" @@ -9,7 +9,7 @@ WyppTypeError: "foo_1" Expecting no return value when calling function `foo`. But the call returns a value of type `str`. -## File test-data-2.0/basics/functionResult2.py +## File file-test-data/basics/functionResult2.py ## Problematic return in line 5: return "foo_" + str(i) diff --git a/python/test-data-2.0/basics/functionResult2.out b/python/file-test-data/basics/functionResult2.out similarity index 100% rename from python/test-data-2.0/basics/functionResult2.out rename to python/file-test-data/basics/functionResult2.out diff --git a/python/test-data-2.0/basics/functionResult2.out_en b/python/file-test-data/basics/functionResult2.out_en similarity index 100% rename from python/test-data-2.0/basics/functionResult2.out_en rename to python/file-test-data/basics/functionResult2.out_en diff --git a/python/test-data-2.0/basics/functionResult2.py b/python/file-test-data/basics/functionResult2.py similarity index 100% rename from python/test-data-2.0/basics/functionResult2.py rename to python/file-test-data/basics/functionResult2.py diff --git a/python/test-data-2.0/basics/functionResult_ok.err b/python/file-test-data/basics/functionResult_ok.err similarity index 100% rename from python/test-data-2.0/basics/functionResult_ok.err rename to python/file-test-data/basics/functionResult_ok.err diff --git a/python/test-data-2.0/basics/functionResult_ok.out b/python/file-test-data/basics/functionResult_ok.out similarity index 100% rename from python/test-data-2.0/basics/functionResult_ok.out rename to python/file-test-data/basics/functionResult_ok.out diff --git a/python/test-data-2.0/basics/functionResult_ok.py b/python/file-test-data/basics/functionResult_ok.py similarity index 100% rename from python/test-data-2.0/basics/functionResult_ok.py rename to python/file-test-data/basics/functionResult_ok.py diff --git a/python/test-data-2.0/basics/iterator.err b/python/file-test-data/basics/iterator.err similarity index 72% rename from python/test-data-2.0/basics/iterator.err rename to python/file-test-data/basics/iterator.err index 6332ba8c..461a166c 100644 --- a/python/test-data-2.0/basics/iterator.err +++ b/python/file-test-data/basics/iterator.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/iterator.py", line 6, in + File "file-test-data/basics/iterator.py", line 6, in g = my_generator() - File "test-data-2.0/basics/iterator.py", line 4, in my_generator + File "file-test-data/basics/iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 @@ -9,7 +9,7 @@ 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 test-data-2.0/basics/iterator.py +## Datei file-test-data/basics/iterator.py ## Rückgabetyp deklariert in Zeile 3: def my_generator() -> Iterator[int]: diff --git a/python/test-data-2.0/basics/iterator.err_en b/python/file-test-data/basics/iterator.err_en similarity index 71% rename from python/test-data-2.0/basics/iterator.err_en rename to python/file-test-data/basics/iterator.err_en index 019d06ef..ce4a4c16 100644 --- a/python/test-data-2.0/basics/iterator.err_en +++ b/python/file-test-data/basics/iterator.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/iterator.py", line 6, in + File "file-test-data/basics/iterator.py", line 6, in g = my_generator() - File "test-data-2.0/basics/iterator.py", line 4, in my_generator + File "file-test-data/basics/iterator.py", line 4, in my_generator return 1 WyppTypeError: 1 @@ -9,7 +9,7 @@ WyppTypeError: 1 Expecting return value of type `Iterator[int]` when calling function `my_generator`. But the call returns a value of type `int`. -## File test-data-2.0/basics/iterator.py +## File file-test-data/basics/iterator.py ## Result type declared in line 3: def my_generator() -> Iterator[int]: diff --git a/python/test-data-2.0/basics/iterator.out b/python/file-test-data/basics/iterator.out similarity index 100% rename from python/test-data-2.0/basics/iterator.out rename to python/file-test-data/basics/iterator.out diff --git a/python/test-data-2.0/basics/iterator.out_en b/python/file-test-data/basics/iterator.out_en similarity index 100% rename from python/test-data-2.0/basics/iterator.out_en rename to python/file-test-data/basics/iterator.out_en diff --git a/python/test-data-2.0/basics/iterator.py b/python/file-test-data/basics/iterator.py similarity index 100% rename from python/test-data-2.0/basics/iterator.py rename to python/file-test-data/basics/iterator.py diff --git a/python/test-data-2.0/basics/iterator_ok.err b/python/file-test-data/basics/iterator_ok.err similarity index 100% rename from python/test-data-2.0/basics/iterator_ok.err rename to python/file-test-data/basics/iterator_ok.err diff --git a/python/test-data-2.0/basics/iterator_ok.out b/python/file-test-data/basics/iterator_ok.out similarity index 100% rename from python/test-data-2.0/basics/iterator_ok.out rename to python/file-test-data/basics/iterator_ok.out diff --git a/python/test-data-2.0/basics/iterator_ok.py b/python/file-test-data/basics/iterator_ok.py similarity index 100% rename from python/test-data-2.0/basics/iterator_ok.py rename to python/file-test-data/basics/iterator_ok.py diff --git a/python/test-data-2.0/basics/kwargs.err b/python/file-test-data/basics/kwargs.err similarity index 76% rename from python/test-data-2.0/basics/kwargs.err rename to python/file-test-data/basics/kwargs.err index 30f46a06..d7c8f199 100644 --- a/python/test-data-2.0/basics/kwargs.err +++ b/python/file-test-data/basics/kwargs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/kwargs.py", line 4, in + File "file-test-data/basics/kwargs.py", line 4, in foo(1, y=1, z=2) WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/basics/kwargs.py +## Datei file-test-data/basics/kwargs.py ## Fehlerhafter Aufruf in Zeile 4: foo(1, y=1, z=2) diff --git a/python/test-data-2.0/basics/kwargs.err_en b/python/file-test-data/basics/kwargs.err_en similarity index 75% rename from python/test-data-2.0/basics/kwargs.err_en rename to python/file-test-data/basics/kwargs.err_en index 00c0cebb..7a482090 100644 --- a/python/test-data-2.0/basics/kwargs.err_en +++ b/python/file-test-data/basics/kwargs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/kwargs.py", line 4, in + File "file-test-data/basics/kwargs.py", line 4, in foo(1, y=1, z=2) WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 The call of function `foo` expects value of type `str` as argument `z`. But the value given has type `int`. -## File test-data-2.0/basics/kwargs.py +## File file-test-data/basics/kwargs.py ## Problematic call in line 4: foo(1, y=1, z=2) diff --git a/python/test-data-2.0/basics/kwargs.out b/python/file-test-data/basics/kwargs.out similarity index 100% rename from python/test-data-2.0/basics/kwargs.out rename to python/file-test-data/basics/kwargs.out diff --git a/python/test-data-2.0/basics/kwargs.out_en b/python/file-test-data/basics/kwargs.out_en similarity index 100% rename from python/test-data-2.0/basics/kwargs.out_en rename to python/file-test-data/basics/kwargs.out_en diff --git a/python/test-data-2.0/basics/kwargs.py b/python/file-test-data/basics/kwargs.py similarity index 100% rename from python/test-data-2.0/basics/kwargs.py rename to python/file-test-data/basics/kwargs.py diff --git a/python/test-data-2.0/basics/kwargs2.err b/python/file-test-data/basics/kwargs2.err similarity index 76% rename from python/test-data-2.0/basics/kwargs2.err rename to python/file-test-data/basics/kwargs2.err index 943d6037..0fbc3b16 100644 --- a/python/test-data-2.0/basics/kwargs2.err +++ b/python/file-test-data/basics/kwargs2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/kwargs2.py", line 4, in + File "file-test-data/basics/kwargs2.py", line 4, in foo(1, z=2, y='foo') WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/basics/kwargs2.py +## Datei file-test-data/basics/kwargs2.py ## Fehlerhafter Aufruf in Zeile 4: foo(1, z=2, y='foo') diff --git a/python/test-data-2.0/basics/kwargs2.err_en b/python/file-test-data/basics/kwargs2.err_en similarity index 75% rename from python/test-data-2.0/basics/kwargs2.err_en rename to python/file-test-data/basics/kwargs2.err_en index 2c71edf8..67437dae 100644 --- a/python/test-data-2.0/basics/kwargs2.err_en +++ b/python/file-test-data/basics/kwargs2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/kwargs2.py", line 4, in + File "file-test-data/basics/kwargs2.py", line 4, in foo(1, z=2, y='foo') WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 The call of function `foo` expects value of type `str` as argument `z`. But the value given has type `int`. -## File test-data-2.0/basics/kwargs2.py +## File file-test-data/basics/kwargs2.py ## Problematic call in line 4: foo(1, z=2, y='foo') diff --git a/python/test-data-2.0/basics/kwargs2.out b/python/file-test-data/basics/kwargs2.out similarity index 100% rename from python/test-data-2.0/basics/kwargs2.out rename to python/file-test-data/basics/kwargs2.out diff --git a/python/test-data-2.0/basics/kwargs2.out_en b/python/file-test-data/basics/kwargs2.out_en similarity index 100% rename from python/test-data-2.0/basics/kwargs2.out_en rename to python/file-test-data/basics/kwargs2.out_en diff --git a/python/test-data-2.0/basics/kwargs2.py b/python/file-test-data/basics/kwargs2.py similarity index 100% rename from python/test-data-2.0/basics/kwargs2.py rename to python/file-test-data/basics/kwargs2.py diff --git a/python/test-data-2.0/basics/kwargs_ok.err b/python/file-test-data/basics/kwargs_ok.err similarity index 100% rename from python/test-data-2.0/basics/kwargs_ok.err rename to python/file-test-data/basics/kwargs_ok.err diff --git a/python/test-data-2.0/basics/kwargs_ok.out b/python/file-test-data/basics/kwargs_ok.out similarity index 100% rename from python/test-data-2.0/basics/kwargs_ok.out rename to python/file-test-data/basics/kwargs_ok.out diff --git a/python/test-data-2.0/basics/kwargs_ok.py b/python/file-test-data/basics/kwargs_ok.py similarity index 100% rename from python/test-data-2.0/basics/kwargs_ok.py rename to python/file-test-data/basics/kwargs_ok.py diff --git a/python/test-data-2.0/basics/listArg.err b/python/file-test-data/basics/listArg.err similarity index 74% rename from python/test-data-2.0/basics/listArg.err rename to python/file-test-data/basics/listArg.err index e2c57b57..57676ddc 100644 --- a/python/test-data-2.0/basics/listArg.err +++ b/python/file-test-data/basics/listArg.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/basics/listArg.py", line 4, in + 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 test-data-2.0/basics/listArg.py +## Datei file-test-data/basics/listArg.py ## Fehlerhafter Aufruf in Zeile 4: foo([1, 2, "3"]) diff --git a/python/test-data-2.0/basics/listArg.err_en b/python/file-test-data/basics/listArg.err_en similarity index 73% rename from python/test-data-2.0/basics/listArg.err_en rename to python/file-test-data/basics/listArg.err_en index d3ed3cef..b6fc16a9 100644 --- a/python/test-data-2.0/basics/listArg.err_en +++ b/python/file-test-data/basics/listArg.err_en @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/basics/listArg.py", line 4, in + 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 test-data-2.0/basics/listArg.py +## File file-test-data/basics/listArg.py ## Problematic call in line 4: foo([1, 2, "3"]) diff --git a/python/test-data-2.0/basics/listArg.out b/python/file-test-data/basics/listArg.out similarity index 100% rename from python/test-data-2.0/basics/listArg.out rename to python/file-test-data/basics/listArg.out diff --git a/python/test-data-2.0/basics/listArg.out_en b/python/file-test-data/basics/listArg.out_en similarity index 100% rename from python/test-data-2.0/basics/listArg.out_en rename to python/file-test-data/basics/listArg.out_en diff --git a/python/test-data-2.0/basics/listArg.py b/python/file-test-data/basics/listArg.py similarity index 100% rename from python/test-data-2.0/basics/listArg.py rename to python/file-test-data/basics/listArg.py diff --git a/python/test-data-2.0/basics/listArg_ok.err b/python/file-test-data/basics/listArg_ok.err similarity index 100% rename from python/test-data-2.0/basics/listArg_ok.err rename to python/file-test-data/basics/listArg_ok.err diff --git a/python/test-data-2.0/basics/listArg_ok.out b/python/file-test-data/basics/listArg_ok.out similarity index 100% rename from python/test-data-2.0/basics/listArg_ok.out rename to python/file-test-data/basics/listArg_ok.out diff --git a/python/test-data-2.0/basics/listArg_ok.py b/python/file-test-data/basics/listArg_ok.py similarity index 100% rename from python/test-data-2.0/basics/listArg_ok.py rename to python/file-test-data/basics/listArg_ok.py diff --git a/python/test-data-2.0/basics/listResult.err b/python/file-test-data/basics/listResult.err similarity index 69% rename from python/test-data-2.0/basics/listResult.err rename to python/file-test-data/basics/listResult.err index adef9ecb..3330f737 100644 --- a/python/test-data-2.0/basics/listResult.err +++ b/python/file-test-data/basics/listResult.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/basics/listResult.py", line 5, in + File "file-test-data/basics/listResult.py", line 5, in foo([1, 2, 3]) - File "test-data-2.0/basics/listResult.py", line 3, in foo + 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 test-data-2.0/basics/listResult.py +## Datei file-test-data/basics/listResult.py ## Rückgabetyp deklariert in Zeile 1: def foo(l: list[int]) -> list[int]: diff --git a/python/test-data-2.0/basics/listResult.err_en b/python/file-test-data/basics/listResult.err_en similarity index 68% rename from python/test-data-2.0/basics/listResult.err_en rename to python/file-test-data/basics/listResult.err_en index 62840e5a..e41debdf 100644 --- a/python/test-data-2.0/basics/listResult.err_en +++ b/python/file-test-data/basics/listResult.err_en @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/basics/listResult.py", line 5, in + File "file-test-data/basics/listResult.py", line 5, in foo([1, 2, 3]) - File "test-data-2.0/basics/listResult.py", line 3, in foo + 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 test-data-2.0/basics/listResult.py +## File file-test-data/basics/listResult.py ## Result type declared in line 1: def foo(l: list[int]) -> list[int]: diff --git a/python/test-data-2.0/basics/listResult.out b/python/file-test-data/basics/listResult.out similarity index 100% rename from python/test-data-2.0/basics/listResult.out rename to python/file-test-data/basics/listResult.out diff --git a/python/test-data-2.0/basics/listResult.out_en b/python/file-test-data/basics/listResult.out_en similarity index 100% rename from python/test-data-2.0/basics/listResult.out_en rename to python/file-test-data/basics/listResult.out_en diff --git a/python/test-data-2.0/basics/listResult.py b/python/file-test-data/basics/listResult.py similarity index 100% rename from python/test-data-2.0/basics/listResult.py rename to python/file-test-data/basics/listResult.py diff --git a/python/test-data-2.0/basics/listResult_ok.err b/python/file-test-data/basics/listResult_ok.err similarity index 100% rename from python/test-data-2.0/basics/listResult_ok.err rename to python/file-test-data/basics/listResult_ok.err diff --git a/python/test-data-2.0/basics/listResult_ok.out b/python/file-test-data/basics/listResult_ok.out similarity index 100% rename from python/test-data-2.0/basics/listResult_ok.out rename to python/file-test-data/basics/listResult_ok.out diff --git a/python/test-data-2.0/basics/listResult_ok.py b/python/file-test-data/basics/listResult_ok.py similarity index 100% rename from python/test-data-2.0/basics/listResult_ok.py rename to python/file-test-data/basics/listResult_ok.py diff --git a/python/test-data-2.0/basics/method.err b/python/file-test-data/basics/method.err similarity index 77% rename from python/test-data-2.0/basics/method.err rename to python/file-test-data/basics/method.err index 3318d9e5..33d6ee80 100644 --- a/python/test-data-2.0/basics/method.err +++ b/python/file-test-data/basics/method.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/method.py", line 9, in + File "file-test-data/basics/method.py", line 9, in c.method("2") WyppTypeError: "2" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/method.py +## Datei file-test-data/basics/method.py ## Fehlerhafter Aufruf in Zeile 9: c.method("2") diff --git a/python/test-data-2.0/basics/method.err_en b/python/file-test-data/basics/method.err_en similarity index 76% rename from python/test-data-2.0/basics/method.err_en rename to python/file-test-data/basics/method.err_en index f0401824..458cc43a 100644 --- a/python/test-data-2.0/basics/method.err_en +++ b/python/file-test-data/basics/method.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/method.py", line 9, in + File "file-test-data/basics/method.py", line 9, in c.method("2") WyppTypeError: "2" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/method.py +## File file-test-data/basics/method.py ## Problematic call in line 9: c.method("2") diff --git a/python/test-data-2.0/basics/method.out b/python/file-test-data/basics/method.out similarity index 100% rename from python/test-data-2.0/basics/method.out rename to python/file-test-data/basics/method.out diff --git a/python/test-data-2.0/basics/method.out_en b/python/file-test-data/basics/method.out_en similarity index 100% rename from python/test-data-2.0/basics/method.out_en rename to python/file-test-data/basics/method.out_en diff --git a/python/test-data-2.0/basics/method.py b/python/file-test-data/basics/method.py similarity index 100% rename from python/test-data-2.0/basics/method.py rename to python/file-test-data/basics/method.py diff --git a/python/test-data-2.0/basics/method_ok.err b/python/file-test-data/basics/method_ok.err similarity index 100% rename from python/test-data-2.0/basics/method_ok.err rename to python/file-test-data/basics/method_ok.err diff --git a/python/test-data-2.0/basics/method_ok.out b/python/file-test-data/basics/method_ok.out similarity index 100% rename from python/test-data-2.0/basics/method_ok.out rename to python/file-test-data/basics/method_ok.out diff --git a/python/test-data-2.0/basics/method_ok.py b/python/file-test-data/basics/method_ok.py similarity index 100% rename from python/test-data-2.0/basics/method_ok.py rename to python/file-test-data/basics/method_ok.py diff --git a/python/test-data-2.0/basics/mutable.err b/python/file-test-data/basics/mutable.err similarity index 74% rename from python/test-data-2.0/basics/mutable.err rename to python/file-test-data/basics/mutable.err index 52e96246..8569f41e 100644 --- a/python/test-data-2.0/basics/mutable.err +++ b/python/file-test-data/basics/mutable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/mutable.py", line 10, in + File "file-test-data/basics/mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" @@ -7,7 +7,7 @@ WyppTypeError: "foo" Attribut `y` des Records `Point` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei test-data-2.0/basics/mutable.py +## Datei file-test-data/basics/mutable.py ## Fehlerhafte Zuweisung in Zeile 10: p.y = "foo" diff --git a/python/test-data-2.0/basics/mutable.err_en b/python/file-test-data/basics/mutable.err_en similarity index 73% rename from python/test-data-2.0/basics/mutable.err_en rename to python/file-test-data/basics/mutable.err_en index 2c1a97f7..1c39452c 100644 --- a/python/test-data-2.0/basics/mutable.err_en +++ b/python/file-test-data/basics/mutable.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/mutable.py", line 10, in + File "file-test-data/basics/mutable.py", line 10, in p.y = "foo" WyppTypeError: "foo" @@ -7,7 +7,7 @@ WyppTypeError: "foo" Attribute `y` of record `Point` declared with type `int.` Cannot set attribute to value of type `str`. -## File test-data-2.0/basics/mutable.py +## File file-test-data/basics/mutable.py ## Problematic assignment in line 10: p.y = "foo" diff --git a/python/test-data-2.0/basics/mutable.out b/python/file-test-data/basics/mutable.out similarity index 100% rename from python/test-data-2.0/basics/mutable.out rename to python/file-test-data/basics/mutable.out diff --git a/python/test-data-2.0/basics/mutable.out_en b/python/file-test-data/basics/mutable.out_en similarity index 100% rename from python/test-data-2.0/basics/mutable.out_en rename to python/file-test-data/basics/mutable.out_en diff --git a/python/test-data-2.0/basics/mutable.py b/python/file-test-data/basics/mutable.py similarity index 100% rename from python/test-data-2.0/basics/mutable.py rename to python/file-test-data/basics/mutable.py diff --git a/python/test-data-2.0/basics/mutable2.err b/python/file-test-data/basics/mutable2.err similarity index 76% rename from python/test-data-2.0/basics/mutable2.err rename to python/file-test-data/basics/mutable2.err index 53f2453c..e3ab1fb4 100644 --- a/python/test-data-2.0/basics/mutable2.err +++ b/python/file-test-data/basics/mutable2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/mutable2.py", line 8, in + File "file-test-data/basics/mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/mutable2.py +## Datei file-test-data/basics/mutable2.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(1, '2') diff --git a/python/test-data-2.0/basics/mutable2.err_en b/python/file-test-data/basics/mutable2.err_en similarity index 75% rename from python/test-data-2.0/basics/mutable2.err_en rename to python/file-test-data/basics/mutable2.err_en index ef2272ab..515ba4ec 100644 --- a/python/test-data-2.0/basics/mutable2.err_en +++ b/python/file-test-data/basics/mutable2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/mutable2.py", line 8, in + File "file-test-data/basics/mutable2.py", line 8, in p = Point(1, '2') WyppTypeError: '2' @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/mutable2.py +## File file-test-data/basics/mutable2.py ## Problematic call in line 8: p = Point(1, '2') diff --git a/python/test-data-2.0/basics/mutable2.out b/python/file-test-data/basics/mutable2.out similarity index 100% rename from python/test-data-2.0/basics/mutable2.out rename to python/file-test-data/basics/mutable2.out diff --git a/python/test-data-2.0/basics/mutable2.out_en b/python/file-test-data/basics/mutable2.out_en similarity index 100% rename from python/test-data-2.0/basics/mutable2.out_en rename to python/file-test-data/basics/mutable2.out_en diff --git a/python/test-data-2.0/basics/mutable2.py b/python/file-test-data/basics/mutable2.py similarity index 100% rename from python/test-data-2.0/basics/mutable2.py rename to python/file-test-data/basics/mutable2.py diff --git a/python/test-data-2.0/basics/mutable2_ok.err b/python/file-test-data/basics/mutable2_ok.err similarity index 100% rename from python/test-data-2.0/basics/mutable2_ok.err rename to python/file-test-data/basics/mutable2_ok.err diff --git a/python/test-data-2.0/basics/mutable2_ok.out b/python/file-test-data/basics/mutable2_ok.out similarity index 100% rename from python/test-data-2.0/basics/mutable2_ok.out rename to python/file-test-data/basics/mutable2_ok.out diff --git a/python/test-data-2.0/basics/mutable2_ok.py b/python/file-test-data/basics/mutable2_ok.py similarity index 100% rename from python/test-data-2.0/basics/mutable2_ok.py rename to python/file-test-data/basics/mutable2_ok.py diff --git a/python/test-data-2.0/basics/mutable_ok.err b/python/file-test-data/basics/mutable_ok.err similarity index 100% rename from python/test-data-2.0/basics/mutable_ok.err rename to python/file-test-data/basics/mutable_ok.err diff --git a/python/test-data-2.0/basics/mutable_ok.out b/python/file-test-data/basics/mutable_ok.out similarity index 100% rename from python/test-data-2.0/basics/mutable_ok.out rename to python/file-test-data/basics/mutable_ok.out diff --git a/python/test-data-2.0/basics/mutable_ok.py b/python/file-test-data/basics/mutable_ok.py similarity index 100% rename from python/test-data-2.0/basics/mutable_ok.py rename to python/file-test-data/basics/mutable_ok.py diff --git a/python/test-data-2.0/basics/nested.err b/python/file-test-data/basics/nested.err similarity index 76% rename from python/test-data-2.0/basics/nested.err rename to python/file-test-data/basics/nested.err index 60ad4340..d814332f 100644 --- a/python/test-data-2.0/basics/nested.err +++ b/python/file-test-data/basics/nested.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/nested.py", line 4, in + File "file-test-data/basics/nested.py", line 4, in foo(42) WyppTypeError: 42 @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/nested.py +## Datei file-test-data/basics/nested.py ## Fehlerhafter Aufruf in Zeile 4: foo(42) diff --git a/python/test-data-2.0/basics/nested.out b/python/file-test-data/basics/nested.out similarity index 100% rename from python/test-data-2.0/basics/nested.out rename to python/file-test-data/basics/nested.out diff --git a/python/test-data-2.0/basics/nested.py b/python/file-test-data/basics/nested.py similarity index 100% rename from python/test-data-2.0/basics/nested.py rename to python/file-test-data/basics/nested.py diff --git a/python/test-data-2.0/basics/nestedFun.err b/python/file-test-data/basics/nestedFun.err similarity index 68% rename from python/test-data-2.0/basics/nestedFun.err rename to python/file-test-data/basics/nestedFun.err index b70bcf7f..84ff8062 100644 --- a/python/test-data-2.0/basics/nestedFun.err +++ b/python/file-test-data/basics/nestedFun.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/nestedFun.py", line 9, in + File "file-test-data/basics/nestedFun.py", line 9, in foo(1) - File "test-data-2.0/basics/nestedFun.py", line 7, in foo + File "file-test-data/basics/nestedFun.py", line 7, in foo return bar("foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ WyppTypeError: "foo" Der Aufruf der Funktion `bar` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/basics/nestedFun.py +## Datei file-test-data/basics/nestedFun.py ## Fehlerhafter Aufruf in Zeile 7: return bar("foo") diff --git a/python/test-data-2.0/basics/nestedFun.err_en b/python/file-test-data/basics/nestedFun.err_en similarity index 67% rename from python/test-data-2.0/basics/nestedFun.err_en rename to python/file-test-data/basics/nestedFun.err_en index fafacf51..e9d766fd 100644 --- a/python/test-data-2.0/basics/nestedFun.err_en +++ b/python/file-test-data/basics/nestedFun.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/nestedFun.py", line 9, in + File "file-test-data/basics/nestedFun.py", line 9, in foo(1) - File "test-data-2.0/basics/nestedFun.py", line 7, in foo + File "file-test-data/basics/nestedFun.py", line 7, in foo return bar("foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ WyppTypeError: "foo" The call of function `bar` expects value of type `int` as 1st argument. But the value given has type `str`. -## File test-data-2.0/basics/nestedFun.py +## File file-test-data/basics/nestedFun.py ## Problematic call in line 7: return bar("foo") diff --git a/python/test-data-2.0/basics/nestedFun.out b/python/file-test-data/basics/nestedFun.out similarity index 100% rename from python/test-data-2.0/basics/nestedFun.out rename to python/file-test-data/basics/nestedFun.out diff --git a/python/test-data-2.0/basics/nestedFun.out_en b/python/file-test-data/basics/nestedFun.out_en similarity index 100% rename from python/test-data-2.0/basics/nestedFun.out_en rename to python/file-test-data/basics/nestedFun.out_en diff --git a/python/test-data-2.0/basics/nestedFun.py b/python/file-test-data/basics/nestedFun.py similarity index 100% rename from python/test-data-2.0/basics/nestedFun.py rename to python/file-test-data/basics/nestedFun.py diff --git a/python/test-data-2.0/basics/nosig_ok.err b/python/file-test-data/basics/nosig_ok.err similarity index 100% rename from python/test-data-2.0/basics/nosig_ok.err rename to python/file-test-data/basics/nosig_ok.err diff --git a/python/test-data-2.0/basics/nosig_ok.out b/python/file-test-data/basics/nosig_ok.out similarity index 100% rename from python/test-data-2.0/basics/nosig_ok.out rename to python/file-test-data/basics/nosig_ok.out diff --git a/python/test-data-2.0/basics/nosig_ok.py b/python/file-test-data/basics/nosig_ok.py similarity index 100% rename from python/test-data-2.0/basics/nosig_ok.py rename to python/file-test-data/basics/nosig_ok.py diff --git a/python/test-data-2.0/basics/optionalArgs.err b/python/file-test-data/basics/optionalArgs.err similarity index 70% rename from python/test-data-2.0/basics/optionalArgs.err rename to python/file-test-data/basics/optionalArgs.err index 78d5ec45..100e8290 100644 --- a/python/test-data-2.0/basics/optionalArgs.err +++ b/python/file-test-data/basics/optionalArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs.py", line 1, in + File "file-test-data/basics/optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Default-Wert des Parameters `s` der Funktion `foo` muss vom Typ `str` sein. Aber der Default-Wert hat Typ `int`. -## Datei test-data-2.0/basics/optionalArgs.py +## Datei file-test-data/basics/optionalArgs.py ## Parameter deklariert in Zeile 1: def foo(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs.err_en b/python/file-test-data/basics/optionalArgs.err_en similarity index 70% rename from python/test-data-2.0/basics/optionalArgs.err_en rename to python/file-test-data/basics/optionalArgs.err_en index f13bf117..0c7ceff6 100644 --- a/python/test-data-2.0/basics/optionalArgs.err_en +++ b/python/file-test-data/basics/optionalArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs.py", line 1, in + File "file-test-data/basics/optionalArgs.py", line 1, in def foo(i: int, s: str=2): WyppTypeError: 2 @@ -7,7 +7,7 @@ WyppTypeError: 2 Default value for parameter `s` of function `foo` must have type `str`. But the default value has type `int`. -## File test-data-2.0/basics/optionalArgs.py +## File file-test-data/basics/optionalArgs.py ## Parameter declared in line 1: def foo(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs.out b/python/file-test-data/basics/optionalArgs.out similarity index 100% rename from python/test-data-2.0/basics/optionalArgs.out rename to python/file-test-data/basics/optionalArgs.out diff --git a/python/test-data-2.0/basics/optionalArgs.out_en b/python/file-test-data/basics/optionalArgs.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalArgs.out_en rename to python/file-test-data/basics/optionalArgs.out_en diff --git a/python/test-data-2.0/basics/optionalArgs.py b/python/file-test-data/basics/optionalArgs.py similarity index 100% rename from python/test-data-2.0/basics/optionalArgs.py rename to python/file-test-data/basics/optionalArgs.py diff --git a/python/test-data-2.0/basics/optionalArgs2.err b/python/file-test-data/basics/optionalArgs2.err similarity index 62% rename from python/test-data-2.0/basics/optionalArgs2.err rename to python/file-test-data/basics/optionalArgs2.err index f7088044..70409aa7 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err +++ b/python/file-test-data/basics/optionalArgs2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs2.py", line 4, in + File "file-test-data/basics/optionalArgs2.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt mindestens 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/basics/optionalArgs2.py +## Datei file-test-data/basics/optionalArgs2.py ## Aufruf in Zeile 4: foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs2.err_en b/python/file-test-data/basics/optionalArgs2.err_en similarity index 60% rename from python/test-data-2.0/basics/optionalArgs2.err_en rename to python/file-test-data/basics/optionalArgs2.err_en index 3b1f6165..f2505277 100644 --- a/python/test-data-2.0/basics/optionalArgs2.err_en +++ b/python/file-test-data/basics/optionalArgs2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs2.py", line 4, in + File "file-test-data/basics/optionalArgs2.py", line 4, in foo(1) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes at least 2 arguments. Given: 1 argument -## File test-data-2.0/basics/optionalArgs2.py +## 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-2.0/basics/optionalArgs2.out b/python/file-test-data/basics/optionalArgs2.out similarity index 100% rename from python/test-data-2.0/basics/optionalArgs2.out rename to python/file-test-data/basics/optionalArgs2.out diff --git a/python/test-data-2.0/basics/optionalArgs2.out_en b/python/file-test-data/basics/optionalArgs2.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalArgs2.out_en rename to python/file-test-data/basics/optionalArgs2.out_en diff --git a/python/test-data-2.0/basics/optionalArgs2.py b/python/file-test-data/basics/optionalArgs2.py similarity index 100% rename from python/test-data-2.0/basics/optionalArgs2.py rename to python/file-test-data/basics/optionalArgs2.py diff --git a/python/test-data-2.0/basics/optionalArgs3.err b/python/file-test-data/basics/optionalArgs3.err similarity index 64% rename from python/test-data-2.0/basics/optionalArgs3.err rename to python/file-test-data/basics/optionalArgs3.err index d67e0af0..7d626a42 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err +++ b/python/file-test-data/basics/optionalArgs3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs3.py", line 4, in + File "file-test-data/basics/optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` akzeptiert höchstens 3 Argumente. Gegeben: 4 Argumente -## Datei test-data-2.0/basics/optionalArgs3.py +## 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/test-data-2.0/basics/optionalArgs3.err_en b/python/file-test-data/basics/optionalArgs3.err_en similarity index 62% rename from python/test-data-2.0/basics/optionalArgs3.err_en rename to python/file-test-data/basics/optionalArgs3.err_en index 4c5a51ae..26e80eb9 100644 --- a/python/test-data-2.0/basics/optionalArgs3.err_en +++ b/python/file-test-data/basics/optionalArgs3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs3.py", line 4, in + File "file-test-data/basics/optionalArgs3.py", line 4, in foo(1, 2, 3, 4) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes at most 3 arguments. Given: 4 arguments -## File test-data-2.0/basics/optionalArgs3.py +## 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-2.0/basics/optionalArgs3.out b/python/file-test-data/basics/optionalArgs3.out similarity index 100% rename from python/test-data-2.0/basics/optionalArgs3.out rename to python/file-test-data/basics/optionalArgs3.out diff --git a/python/test-data-2.0/basics/optionalArgs3.out_en b/python/file-test-data/basics/optionalArgs3.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalArgs3.out_en rename to python/file-test-data/basics/optionalArgs3.out_en diff --git a/python/test-data-2.0/basics/optionalArgs3.py b/python/file-test-data/basics/optionalArgs3.py similarity index 100% rename from python/test-data-2.0/basics/optionalArgs3.py rename to python/file-test-data/basics/optionalArgs3.py diff --git a/python/test-data-2.0/basics/optionalArgs4.err b/python/file-test-data/basics/optionalArgs4.err similarity index 64% rename from python/test-data-2.0/basics/optionalArgs4.err rename to python/file-test-data/basics/optionalArgs4.err index 6f558311..642f35ba 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err +++ b/python/file-test-data/basics/optionalArgs4.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs4.py", line 1, in + File "file-test-data/basics/optionalArgs4.py", line 1, in class C: - File "test-data-2.0/basics/optionalArgs4.py", line 2, in C + File "file-test-data/basics/optionalArgs4.py", line 2, in C def __init__(i: int, s: str=2): WyppTypeError: 2 @@ -9,7 +9,7 @@ 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 test-data-2.0/basics/optionalArgs4.py +## Datei file-test-data/basics/optionalArgs4.py ## Parameter deklariert in Zeile 2: def __init__(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs4.err_en b/python/file-test-data/basics/optionalArgs4.err_en similarity index 63% rename from python/test-data-2.0/basics/optionalArgs4.err_en rename to python/file-test-data/basics/optionalArgs4.err_en index 2a28d988..bef9dca7 100644 --- a/python/test-data-2.0/basics/optionalArgs4.err_en +++ b/python/file-test-data/basics/optionalArgs4.err_en @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalArgs4.py", line 1, in + File "file-test-data/basics/optionalArgs4.py", line 1, in class C: - File "test-data-2.0/basics/optionalArgs4.py", line 2, in C + File "file-test-data/basics/optionalArgs4.py", line 2, in C def __init__(i: int, s: str=2): WyppTypeError: 2 @@ -9,7 +9,7 @@ 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 test-data-2.0/basics/optionalArgs4.py +## File file-test-data/basics/optionalArgs4.py ## Parameter declared in line 2: def __init__(i: int, s: str=2): \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalArgs4.out b/python/file-test-data/basics/optionalArgs4.out similarity index 100% rename from python/test-data-2.0/basics/optionalArgs4.out rename to python/file-test-data/basics/optionalArgs4.out diff --git a/python/test-data-2.0/basics/optionalArgs4.out_en b/python/file-test-data/basics/optionalArgs4.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalArgs4.out_en rename to python/file-test-data/basics/optionalArgs4.out_en diff --git a/python/test-data-2.0/basics/optionalArgs4.py b/python/file-test-data/basics/optionalArgs4.py similarity index 100% rename from python/test-data-2.0/basics/optionalArgs4.py rename to python/file-test-data/basics/optionalArgs4.py diff --git a/python/test-data-2.0/basics/optionalArgs_ok.err b/python/file-test-data/basics/optionalArgs_ok.err similarity index 100% rename from python/test-data-2.0/basics/optionalArgs_ok.err rename to python/file-test-data/basics/optionalArgs_ok.err diff --git a/python/test-data-2.0/basics/optionalArgs_ok.out b/python/file-test-data/basics/optionalArgs_ok.out similarity index 100% rename from python/test-data-2.0/basics/optionalArgs_ok.out rename to python/file-test-data/basics/optionalArgs_ok.out diff --git a/python/test-data-2.0/basics/optionalArgs_ok.py b/python/file-test-data/basics/optionalArgs_ok.py similarity index 100% rename from python/test-data-2.0/basics/optionalArgs_ok.py rename to python/file-test-data/basics/optionalArgs_ok.py diff --git a/python/test-data-2.0/basics/optionalAttr.err b/python/file-test-data/basics/optionalAttr.err similarity index 68% rename from python/test-data-2.0/basics/optionalAttr.err rename to python/file-test-data/basics/optionalAttr.err index 33b11423..43d19e81 100644 --- a/python/test-data-2.0/basics/optionalAttr.err +++ b/python/file-test-data/basics/optionalAttr.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr.py", line 3, in + File "file-test-data/basics/optionalAttr.py", line 3, in @record WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' Default-Wert des Attributs `y` des Records `C` muss vom Typ `int` sein. Aber der Default-Wert hat Typ `str`. -## Datei test-data-2.0/basics/optionalAttr.py +## Datei file-test-data/basics/optionalAttr.py ## Parameter deklariert in Zeile 6: y: int = 'foo' \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr.err_en b/python/file-test-data/basics/optionalAttr.err_en similarity index 68% rename from python/test-data-2.0/basics/optionalAttr.err_en rename to python/file-test-data/basics/optionalAttr.err_en index de6543dd..21401c73 100644 --- a/python/test-data-2.0/basics/optionalAttr.err_en +++ b/python/file-test-data/basics/optionalAttr.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr.py", line 3, in + File "file-test-data/basics/optionalAttr.py", line 3, in @record WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' Default value for attribute `y` of record `C` must have type `int`. But the default value has type `str`. -## File test-data-2.0/basics/optionalAttr.py +## File file-test-data/basics/optionalAttr.py ## Parameter declared in line 6: y: int = 'foo' \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr.out b/python/file-test-data/basics/optionalAttr.out similarity index 100% rename from python/test-data-2.0/basics/optionalAttr.out rename to python/file-test-data/basics/optionalAttr.out diff --git a/python/test-data-2.0/basics/optionalAttr.out_en b/python/file-test-data/basics/optionalAttr.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalAttr.out_en rename to python/file-test-data/basics/optionalAttr.out_en diff --git a/python/test-data-2.0/basics/optionalAttr.py b/python/file-test-data/basics/optionalAttr.py similarity index 100% rename from python/test-data-2.0/basics/optionalAttr.py rename to python/file-test-data/basics/optionalAttr.py diff --git a/python/test-data-2.0/basics/optionalAttr2.err b/python/file-test-data/basics/optionalAttr2.err similarity index 66% rename from python/test-data-2.0/basics/optionalAttr2.err rename to python/file-test-data/basics/optionalAttr2.err index b93e0b2b..98e5620a 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err +++ b/python/file-test-data/basics/optionalAttr2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr2.py", line 9, in + File "file-test-data/basics/optionalAttr2.py", line 9, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt mindestens 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/basics/optionalAttr2.py +## Datei file-test-data/basics/optionalAttr2.py ## Aufruf in Zeile 9: c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr2.err_en b/python/file-test-data/basics/optionalAttr2.err_en similarity index 64% rename from python/test-data-2.0/basics/optionalAttr2.err_en rename to python/file-test-data/basics/optionalAttr2.err_en index 7c78815c..030f5e0a 100644 --- a/python/test-data-2.0/basics/optionalAttr2.err_en +++ b/python/file-test-data/basics/optionalAttr2.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr2.py", line 9, in + File "file-test-data/basics/optionalAttr2.py", line 9, in c = C(1) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes at least 2 arguments. Given: 1 argument -## File test-data-2.0/basics/optionalAttr2.py +## File file-test-data/basics/optionalAttr2.py ## Call in line 9: c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr2.out b/python/file-test-data/basics/optionalAttr2.out similarity index 100% rename from python/test-data-2.0/basics/optionalAttr2.out rename to python/file-test-data/basics/optionalAttr2.out diff --git a/python/test-data-2.0/basics/optionalAttr2.out_en b/python/file-test-data/basics/optionalAttr2.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalAttr2.out_en rename to python/file-test-data/basics/optionalAttr2.out_en diff --git a/python/test-data-2.0/basics/optionalAttr2.py b/python/file-test-data/basics/optionalAttr2.py similarity index 100% rename from python/test-data-2.0/basics/optionalAttr2.py rename to python/file-test-data/basics/optionalAttr2.py diff --git a/python/test-data-2.0/basics/optionalAttr3.err b/python/file-test-data/basics/optionalAttr3.err similarity index 67% rename from python/test-data-2.0/basics/optionalAttr3.err rename to python/file-test-data/basics/optionalAttr3.err index c6f0bb6e..9b406713 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err +++ b/python/file-test-data/basics/optionalAttr3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr3.py", line 9, in + File "file-test-data/basics/optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` akzeptiert höchstens 3 Argumente. Gegeben: 4 Argumente -## Datei test-data-2.0/basics/optionalAttr3.py +## Datei file-test-data/basics/optionalAttr3.py ## Aufruf in Zeile 9: c = C(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr3.err_en b/python/file-test-data/basics/optionalAttr3.err_en similarity index 65% rename from python/test-data-2.0/basics/optionalAttr3.err_en rename to python/file-test-data/basics/optionalAttr3.err_en index b4637346..17976c56 100644 --- a/python/test-data-2.0/basics/optionalAttr3.err_en +++ b/python/file-test-data/basics/optionalAttr3.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/optionalAttr3.py", line 9, in + File "file-test-data/basics/optionalAttr3.py", line 9, in c = C(1, 2, 3, 4) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes at most 3 arguments. Given: 4 arguments -## File test-data-2.0/basics/optionalAttr3.py +## File file-test-data/basics/optionalAttr3.py ## Call in line 9: c = C(1, 2, 3, 4) \ No newline at end of file diff --git a/python/test-data-2.0/basics/optionalAttr3.out b/python/file-test-data/basics/optionalAttr3.out similarity index 100% rename from python/test-data-2.0/basics/optionalAttr3.out rename to python/file-test-data/basics/optionalAttr3.out diff --git a/python/test-data-2.0/basics/optionalAttr3.out_en b/python/file-test-data/basics/optionalAttr3.out_en similarity index 100% rename from python/test-data-2.0/basics/optionalAttr3.out_en rename to python/file-test-data/basics/optionalAttr3.out_en diff --git a/python/test-data-2.0/basics/optionalAttr3.py b/python/file-test-data/basics/optionalAttr3.py similarity index 100% rename from python/test-data-2.0/basics/optionalAttr3.py rename to python/file-test-data/basics/optionalAttr3.py diff --git a/python/test-data-2.0/basics/optionalAttr_ok.err b/python/file-test-data/basics/optionalAttr_ok.err similarity index 100% rename from python/test-data-2.0/basics/optionalAttr_ok.err rename to python/file-test-data/basics/optionalAttr_ok.err diff --git a/python/test-data-2.0/basics/optionalAttr_ok.out b/python/file-test-data/basics/optionalAttr_ok.out similarity index 100% rename from python/test-data-2.0/basics/optionalAttr_ok.out rename to python/file-test-data/basics/optionalAttr_ok.out diff --git a/python/test-data-2.0/basics/optionalAttr_ok.py b/python/file-test-data/basics/optionalAttr_ok.py similarity index 100% rename from python/test-data-2.0/basics/optionalAttr_ok.py rename to python/file-test-data/basics/optionalAttr_ok.py diff --git a/python/test-data-2.0/basics/partial.err b/python/file-test-data/basics/partial.err similarity index 68% rename from python/test-data-2.0/basics/partial.err rename to python/file-test-data/basics/partial.err index 6c1f7809..23401695 100644 --- a/python/test-data-2.0/basics/partial.err +++ b/python/file-test-data/basics/partial.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/basics/partial.py", line 1, in + 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 test-data-2.0/basics/partial.py +## Datei file-test-data/basics/partial.py ## Parameter deklariert in Zeile 1: def foo(i: int, j) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/basics/partial.err_en b/python/file-test-data/basics/partial.err_en similarity index 68% rename from python/test-data-2.0/basics/partial.err_en rename to python/file-test-data/basics/partial.err_en index f14c885b..6f3cb523 100644 --- a/python/test-data-2.0/basics/partial.err_en +++ b/python/file-test-data/basics/partial.err_en @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/basics/partial.py", line 1, in + 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 test-data-2.0/basics/partial.py +## File file-test-data/basics/partial.py ## Parameter declared in line 1: def foo(i: int, j) -> None: \ No newline at end of file diff --git a/python/test-data-2.0/basics/partial.out b/python/file-test-data/basics/partial.out similarity index 100% rename from python/test-data-2.0/basics/partial.out rename to python/file-test-data/basics/partial.out diff --git a/python/test-data-2.0/basics/partial.out_en b/python/file-test-data/basics/partial.out_en similarity index 100% rename from python/test-data-2.0/basics/partial.out_en rename to python/file-test-data/basics/partial.out_en diff --git a/python/test-data-2.0/basics/partial.py b/python/file-test-data/basics/partial.py similarity index 100% rename from python/test-data-2.0/basics/partial.py rename to python/file-test-data/basics/partial.py diff --git a/python/test-data-2.0/basics/record.err b/python/file-test-data/basics/record.err similarity index 78% rename from python/test-data-2.0/basics/record.err rename to python/file-test-data/basics/record.err index c69b34b5..b2daf4fd 100644 --- a/python/test-data-2.0/basics/record.err +++ b/python/file-test-data/basics/record.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/record.py", line 8, in + File "file-test-data/basics/record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/record.py +## Datei file-test-data/basics/record.py ## Fehlerhafter Aufruf in Zeile 8: p = Person("Alice", "30") diff --git a/python/test-data-2.0/basics/record.err_en b/python/file-test-data/basics/record.err_en similarity index 77% rename from python/test-data-2.0/basics/record.err_en rename to python/file-test-data/basics/record.err_en index 524ea4ad..d3e78da1 100644 --- a/python/test-data-2.0/basics/record.err_en +++ b/python/file-test-data/basics/record.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/record.py", line 8, in + File "file-test-data/basics/record.py", line 8, in p = Person("Alice", "30") WyppTypeError: "30" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/record.py +## File file-test-data/basics/record.py ## Problematic call in line 8: p = Person("Alice", "30") diff --git a/python/test-data-2.0/basics/record.out b/python/file-test-data/basics/record.out similarity index 100% rename from python/test-data-2.0/basics/record.out rename to python/file-test-data/basics/record.out diff --git a/python/test-data-2.0/basics/record.out_en b/python/file-test-data/basics/record.out_en similarity index 100% rename from python/test-data-2.0/basics/record.out_en rename to python/file-test-data/basics/record.out_en diff --git a/python/test-data-2.0/basics/record.py b/python/file-test-data/basics/record.py similarity index 100% rename from python/test-data-2.0/basics/record.py rename to python/file-test-data/basics/record.py diff --git a/python/test-data-2.0/basics/record_ok.err b/python/file-test-data/basics/record_ok.err similarity index 100% rename from python/test-data-2.0/basics/record_ok.err rename to python/file-test-data/basics/record_ok.err diff --git a/python/test-data-2.0/basics/record_ok.out b/python/file-test-data/basics/record_ok.out similarity index 100% rename from python/test-data-2.0/basics/record_ok.out rename to python/file-test-data/basics/record_ok.out diff --git a/python/test-data-2.0/basics/record_ok.py b/python/file-test-data/basics/record_ok.py similarity index 100% rename from python/test-data-2.0/basics/record_ok.py rename to python/file-test-data/basics/record_ok.py diff --git a/python/test-data-2.0/basics/sample_ok.err b/python/file-test-data/basics/sample_ok.err similarity index 100% rename from python/test-data-2.0/basics/sample_ok.err rename to python/file-test-data/basics/sample_ok.err diff --git a/python/test-data-2.0/basics/sample_ok.out b/python/file-test-data/basics/sample_ok.out similarity index 100% rename from python/test-data-2.0/basics/sample_ok.out rename to python/file-test-data/basics/sample_ok.out diff --git a/python/test-data-2.0/basics/sample_ok.py b/python/file-test-data/basics/sample_ok.py similarity index 100% rename from python/test-data-2.0/basics/sample_ok.py rename to python/file-test-data/basics/sample_ok.py 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-2.0/basics/stack.out b/python/file-test-data/basics/stack.out similarity index 100% rename from python/test-data-2.0/basics/stack.out rename to python/file-test-data/basics/stack.out diff --git a/python/test-data-2.0/basics/stack.py b/python/file-test-data/basics/stack.py similarity index 100% rename from python/test-data-2.0/basics/stack.py rename to python/file-test-data/basics/stack.py diff --git a/python/test-data-2.0/basics/staticmethod.err b/python/file-test-data/basics/staticmethod.err similarity index 74% rename from python/test-data-2.0/basics/staticmethod.err rename to python/file-test-data/basics/staticmethod.err index 9232e6ac..dbffb013 100644 --- a/python/test-data-2.0/basics/staticmethod.err +++ b/python/file-test-data/basics/staticmethod.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/staticmethod.py", line 6, in + File "file-test-data/basics/staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/staticmethod.py +## Datei file-test-data/basics/staticmethod.py ## Fehlerhafter Aufruf in Zeile 6: C.method("2") diff --git a/python/test-data-2.0/basics/staticmethod.err_en b/python/file-test-data/basics/staticmethod.err_en similarity index 73% rename from python/test-data-2.0/basics/staticmethod.err_en rename to python/file-test-data/basics/staticmethod.err_en index 5fb1b1d9..70642fac 100644 --- a/python/test-data-2.0/basics/staticmethod.err_en +++ b/python/file-test-data/basics/staticmethod.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/staticmethod.py", line 6, in + File "file-test-data/basics/staticmethod.py", line 6, in C.method("2") WyppTypeError: "2" @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/staticmethod.py +## File file-test-data/basics/staticmethod.py ## Problematic call in line 6: C.method("2") diff --git a/python/test-data-2.0/basics/staticmethod.out b/python/file-test-data/basics/staticmethod.out similarity index 100% rename from python/test-data-2.0/basics/staticmethod.out rename to python/file-test-data/basics/staticmethod.out diff --git a/python/test-data-2.0/basics/staticmethod.out_en b/python/file-test-data/basics/staticmethod.out_en similarity index 100% rename from python/test-data-2.0/basics/staticmethod.out_en rename to python/file-test-data/basics/staticmethod.out_en diff --git a/python/test-data-2.0/basics/staticmethod.py b/python/file-test-data/basics/staticmethod.py similarity index 100% rename from python/test-data-2.0/basics/staticmethod.py rename to python/file-test-data/basics/staticmethod.py diff --git a/python/test-data-2.0/basics/staticmethod_ok.err b/python/file-test-data/basics/staticmethod_ok.err similarity index 100% rename from python/test-data-2.0/basics/staticmethod_ok.err rename to python/file-test-data/basics/staticmethod_ok.err diff --git a/python/test-data-2.0/basics/staticmethod_ok.out b/python/file-test-data/basics/staticmethod_ok.out similarity index 100% rename from python/test-data-2.0/basics/staticmethod_ok.out rename to python/file-test-data/basics/staticmethod_ok.out diff --git a/python/test-data-2.0/basics/staticmethod_ok.py b/python/file-test-data/basics/staticmethod_ok.py similarity index 100% rename from python/test-data-2.0/basics/staticmethod_ok.py rename to python/file-test-data/basics/staticmethod_ok.py diff --git a/python/test-data-2.0/basics/testCallable.err b/python/file-test-data/basics/testCallable.err similarity index 75% rename from python/test-data-2.0/basics/testCallable.err rename to python/file-test-data/basics/testCallable.err index 7a803368..5fe14904 100644 --- a/python/test-data-2.0/basics/testCallable.err +++ b/python/file-test-data/basics/testCallable.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/testCallable.py", line 6, in + File "file-test-data/basics/testCallable.py", line 6, in foo(42) WyppTypeError: 42 @@ -7,7 +7,7 @@ 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 test-data-2.0/basics/testCallable.py +## Datei file-test-data/basics/testCallable.py ## Fehlerhafter Aufruf in Zeile 6: foo(42) diff --git a/python/test-data-2.0/basics/testCallable.out b/python/file-test-data/basics/testCallable.out similarity index 100% rename from python/test-data-2.0/basics/testCallable.out rename to python/file-test-data/basics/testCallable.out diff --git a/python/test-data-2.0/basics/testCallable.py b/python/file-test-data/basics/testCallable.py similarity index 100% rename from python/test-data-2.0/basics/testCallable.py rename to python/file-test-data/basics/testCallable.py diff --git a/python/test-data-2.0/basics/tooFewArgs.err b/python/file-test-data/basics/tooFewArgs.err similarity index 62% rename from python/test-data-2.0/basics/tooFewArgs.err rename to python/file-test-data/basics/tooFewArgs.err index 2e1c78cd..6d7678ef 100644 --- a/python/test-data-2.0/basics/tooFewArgs.err +++ b/python/file-test-data/basics/tooFewArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooFewArgs.py", line 4, in + File "file-test-data/basics/tooFewArgs.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/basics/tooFewArgs.py +## 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-2.0/basics/tooFewArgs.out b/python/file-test-data/basics/tooFewArgs.out similarity index 100% rename from python/test-data-2.0/basics/tooFewArgs.out rename to python/file-test-data/basics/tooFewArgs.out diff --git a/python/test-data-2.0/basics/tooFewArgs.py b/python/file-test-data/basics/tooFewArgs.py similarity index 100% rename from python/test-data-2.0/basics/tooFewArgs.py rename to python/file-test-data/basics/tooFewArgs.py diff --git a/python/test-data-2.0/basics/tooFewAttrs.err b/python/file-test-data/basics/tooFewAttrs.err similarity index 65% rename from python/test-data-2.0/basics/tooFewAttrs.err rename to python/file-test-data/basics/tooFewAttrs.err index 45e49425..37b19e92 100644 --- a/python/test-data-2.0/basics/tooFewAttrs.err +++ b/python/file-test-data/basics/tooFewAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooFewAttrs.py", line 8, in + File "file-test-data/basics/tooFewAttrs.py", line 8, in c = C(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/basics/tooFewAttrs.py +## Datei file-test-data/basics/tooFewAttrs.py ## Aufruf in Zeile 8: c = C(1) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooFewAttrs.out b/python/file-test-data/basics/tooFewAttrs.out similarity index 100% rename from python/test-data-2.0/basics/tooFewAttrs.out rename to python/file-test-data/basics/tooFewAttrs.out diff --git a/python/test-data-2.0/basics/tooFewAttrs.py b/python/file-test-data/basics/tooFewAttrs.py similarity index 100% rename from python/test-data-2.0/basics/tooFewAttrs.py rename to python/file-test-data/basics/tooFewAttrs.py diff --git a/python/test-data-2.0/basics/tooManyArgs.err b/python/file-test-data/basics/tooManyArgs.err similarity index 63% rename from python/test-data-2.0/basics/tooManyArgs.err rename to python/file-test-data/basics/tooManyArgs.err index 7ae19a03..c9a0ba95 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err +++ b/python/file-test-data/basics/tooManyArgs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooManyArgs.py", line 4, in + File "file-test-data/basics/tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 3 Argumente -## Datei test-data-2.0/basics/tooManyArgs.py +## 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/test-data-2.0/basics/tooManyArgs.err_en b/python/file-test-data/basics/tooManyArgs.err_en similarity index 61% rename from python/test-data-2.0/basics/tooManyArgs.err_en rename to python/file-test-data/basics/tooManyArgs.err_en index 4c74459f..31e349f4 100644 --- a/python/test-data-2.0/basics/tooManyArgs.err_en +++ b/python/file-test-data/basics/tooManyArgs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooManyArgs.py", line 4, in + File "file-test-data/basics/tooManyArgs.py", line 4, in foo(1, 2, 3) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Function `foo` takes 2 arguments. Given: 3 arguments -## File test-data-2.0/basics/tooManyArgs.py +## 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-2.0/basics/tooManyArgs.out b/python/file-test-data/basics/tooManyArgs.out similarity index 100% rename from python/test-data-2.0/basics/tooManyArgs.out rename to python/file-test-data/basics/tooManyArgs.out diff --git a/python/test-data-2.0/basics/tooManyArgs.out_en b/python/file-test-data/basics/tooManyArgs.out_en similarity index 100% rename from python/test-data-2.0/basics/tooManyArgs.out_en rename to python/file-test-data/basics/tooManyArgs.out_en diff --git a/python/test-data-2.0/basics/tooManyArgs.py b/python/file-test-data/basics/tooManyArgs.py similarity index 100% rename from python/test-data-2.0/basics/tooManyArgs.py rename to python/file-test-data/basics/tooManyArgs.py diff --git a/python/test-data-2.0/basics/tooManyAttrs.err b/python/file-test-data/basics/tooManyAttrs.err similarity index 66% rename from python/test-data-2.0/basics/tooManyAttrs.err rename to python/file-test-data/basics/tooManyAttrs.err index 9e4f839e..8747fc40 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err +++ b/python/file-test-data/basics/tooManyAttrs.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooManyAttrs.py", line 8, in + File "file-test-data/basics/tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Konstruktor des Records `C` benötigt 2 Argumente. Gegeben: 3 Argumente -## Datei test-data-2.0/basics/tooManyAttrs.py +## Datei file-test-data/basics/tooManyAttrs.py ## Aufruf in Zeile 8: c = C(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyAttrs.err_en b/python/file-test-data/basics/tooManyAttrs.err_en similarity index 64% rename from python/test-data-2.0/basics/tooManyAttrs.err_en rename to python/file-test-data/basics/tooManyAttrs.err_en index 9f6f760d..af5fe59b 100644 --- a/python/test-data-2.0/basics/tooManyAttrs.err_en +++ b/python/file-test-data/basics/tooManyAttrs.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/tooManyAttrs.py", line 8, in + File "file-test-data/basics/tooManyAttrs.py", line 8, in c = C(1, 2, 3) WyppTypeError: argument count mismatch @@ -7,7 +7,7 @@ WyppTypeError: argument count mismatch Constructor of record `C` takes 2 arguments. Given: 3 arguments -## File test-data-2.0/basics/tooManyAttrs.py +## File file-test-data/basics/tooManyAttrs.py ## Call in line 8: c = C(1, 2, 3) \ No newline at end of file diff --git a/python/test-data-2.0/basics/tooManyAttrs.out b/python/file-test-data/basics/tooManyAttrs.out similarity index 100% rename from python/test-data-2.0/basics/tooManyAttrs.out rename to python/file-test-data/basics/tooManyAttrs.out diff --git a/python/test-data-2.0/basics/tooManyAttrs.out_en b/python/file-test-data/basics/tooManyAttrs.out_en similarity index 100% rename from python/test-data-2.0/basics/tooManyAttrs.out_en rename to python/file-test-data/basics/tooManyAttrs.out_en diff --git a/python/test-data-2.0/basics/tooManyAttrs.py b/python/file-test-data/basics/tooManyAttrs.py similarity index 100% rename from python/test-data-2.0/basics/tooManyAttrs.py rename to python/file-test-data/basics/tooManyAttrs.py diff --git a/python/test-data-2.0/basics/typeAlias.err b/python/file-test-data/basics/typeAlias.err similarity index 73% rename from python/test-data-2.0/basics/typeAlias.err rename to python/file-test-data/basics/typeAlias.err index 4cf634bb..c7216234 100644 --- a/python/test-data-2.0/basics/typeAlias.err +++ b/python/file-test-data/basics/typeAlias.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/typeAlias.py", line 7, in + File "file-test-data/basics/typeAlias.py", line 7, in foo('foo') WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' Der Aufruf der Funktion `foo` erwartet Wert vom Typ `T` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/basics/typeAlias.py +## Datei file-test-data/basics/typeAlias.py ## Fehlerhafter Aufruf in Zeile 7: foo('foo') diff --git a/python/test-data-2.0/basics/typeAlias.err_en b/python/file-test-data/basics/typeAlias.err_en similarity index 72% rename from python/test-data-2.0/basics/typeAlias.err_en rename to python/file-test-data/basics/typeAlias.err_en index 498192e5..62b6d3f5 100644 --- a/python/test-data-2.0/basics/typeAlias.err_en +++ b/python/file-test-data/basics/typeAlias.err_en @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/basics/typeAlias.py", line 7, in + File "file-test-data/basics/typeAlias.py", line 7, in foo('foo') WyppTypeError: 'foo' @@ -7,7 +7,7 @@ WyppTypeError: 'foo' The call of function `foo` expects value of type `T` as 1st argument. But the value given has type `str`. -## File test-data-2.0/basics/typeAlias.py +## File file-test-data/basics/typeAlias.py ## Problematic call in line 7: foo('foo') diff --git a/python/test-data-2.0/basics/typeAlias.out b/python/file-test-data/basics/typeAlias.out similarity index 100% rename from python/test-data-2.0/basics/typeAlias.out rename to python/file-test-data/basics/typeAlias.out diff --git a/python/test-data-2.0/basics/typeAlias.out_en b/python/file-test-data/basics/typeAlias.out_en similarity index 100% rename from python/test-data-2.0/basics/typeAlias.out_en rename to python/file-test-data/basics/typeAlias.out_en diff --git a/python/test-data-2.0/basics/typeAlias.py b/python/file-test-data/basics/typeAlias.py similarity index 100% rename from python/test-data-2.0/basics/typeAlias.py rename to python/file-test-data/basics/typeAlias.py diff --git a/python/test-data-2.0/basics/typeAlias2.err b/python/file-test-data/basics/typeAlias2.err similarity index 71% rename from python/test-data-2.0/basics/typeAlias2.err rename to python/file-test-data/basics/typeAlias2.err index cbe3330c..45d2db1d 100644 --- a/python/test-data-2.0/basics/typeAlias2.err +++ b/python/file-test-data/basics/typeAlias2.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/basics/typeAlias2.py", line 7, in + 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 test-data-2.0/basics/typeAlias2.py +## Datei file-test-data/basics/typeAlias2.py ## Fehlerhafter Aufruf in Zeile 7: foo(['foo']) diff --git a/python/test-data-2.0/basics/typeAlias2.err_en b/python/file-test-data/basics/typeAlias2.err_en similarity index 70% rename from python/test-data-2.0/basics/typeAlias2.err_en rename to python/file-test-data/basics/typeAlias2.err_en index 61d38cb3..457dd101 100644 --- a/python/test-data-2.0/basics/typeAlias2.err_en +++ b/python/file-test-data/basics/typeAlias2.err_en @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/basics/typeAlias2.py", line 7, in + 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 test-data-2.0/basics/typeAlias2.py +## File file-test-data/basics/typeAlias2.py ## Problematic call in line 7: foo(['foo']) diff --git a/python/test-data-2.0/basics/typeAlias2.out b/python/file-test-data/basics/typeAlias2.out similarity index 100% rename from python/test-data-2.0/basics/typeAlias2.out rename to python/file-test-data/basics/typeAlias2.out diff --git a/python/test-data-2.0/basics/typeAlias2.out_en b/python/file-test-data/basics/typeAlias2.out_en similarity index 100% rename from python/test-data-2.0/basics/typeAlias2.out_en rename to python/file-test-data/basics/typeAlias2.out_en diff --git a/python/test-data-2.0/basics/typeAlias2.py b/python/file-test-data/basics/typeAlias2.py similarity index 100% rename from python/test-data-2.0/basics/typeAlias2.py rename to python/file-test-data/basics/typeAlias2.py diff --git a/python/test-data-2.0/extras/admin_ok.err b/python/file-test-data/extras/admin_ok.err similarity index 100% rename from python/test-data-2.0/extras/admin_ok.err rename to python/file-test-data/extras/admin_ok.err diff --git a/python/test-data-2.0/extras/admin_ok.out b/python/file-test-data/extras/admin_ok.out similarity index 100% rename from python/test-data-2.0/extras/admin_ok.out rename to python/file-test-data/extras/admin_ok.out diff --git a/python/test-data-2.0/extras/admin_ok.py b/python/file-test-data/extras/admin_ok.py similarity index 100% rename from python/test-data-2.0/extras/admin_ok.py rename to python/file-test-data/extras/admin_ok.py diff --git a/python/test-data-2.0/extras/declared-at-missing.err b/python/file-test-data/extras/declared-at-missing.err similarity index 100% rename from python/test-data-2.0/extras/declared-at-missing.err rename to python/file-test-data/extras/declared-at-missing.err diff --git a/python/test-data-2.0/extras/declared-at-missing.err-3.10 b/python/file-test-data/extras/declared-at-missing.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/declared-at-missing.err-3.10 rename to python/file-test-data/extras/declared-at-missing.err-3.10 diff --git a/python/test-data-2.0/extras/declared-at-missing.err-3.11 b/python/file-test-data/extras/declared-at-missing.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/declared-at-missing.err-3.11 rename to python/file-test-data/extras/declared-at-missing.err-3.11 diff --git a/python/test-data-2.0/extras/declared-at-missing.err-3.12 b/python/file-test-data/extras/declared-at-missing.err-3.12 similarity index 79% rename from python/test-data-2.0/extras/declared-at-missing.err-3.12 rename to python/file-test-data/extras/declared-at-missing.err-3.12 index 0e0d4aca..4393d1f0 100644 --- a/python/test-data-2.0/extras/declared-at-missing.err-3.12 +++ b/python/file-test-data/extras/declared-at-missing.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/declared-at-missing.py", line 22, in + 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 test-data-2.0/extras/declared-at-missing.py +## Datei file-test-data/extras/declared-at-missing.py ## Fehlerhafter Aufruf in Zeile 22: semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) diff --git a/python/test-data-2.0/extras/declared-at-missing.out b/python/file-test-data/extras/declared-at-missing.out similarity index 100% rename from python/test-data-2.0/extras/declared-at-missing.out rename to python/file-test-data/extras/declared-at-missing.out diff --git a/python/test-data-2.0/extras/declared-at-missing.py b/python/file-test-data/extras/declared-at-missing.py similarity index 100% rename from python/test-data-2.0/extras/declared-at-missing.py rename to python/file-test-data/extras/declared-at-missing.py diff --git a/python/test-data-2.0/extras/invalidRecord.err b/python/file-test-data/extras/invalidRecord.err similarity index 93% rename from python/test-data-2.0/extras/invalidRecord.err rename to python/file-test-data/extras/invalidRecord.err index 8c9f2f7d..25ad0647 100644 --- a/python/test-data-2.0/extras/invalidRecord.err +++ b/python/file-test-data/extras/invalidRecord.err @@ -12,7 +12,7 @@ Traceback (most recent call last): WyppTypeError: ungültige Record-Definition -## Datei test-data-2.0/extras/invalidRecord.py +## Datei file-test-data/extras/invalidRecord.py ## Zeile 1: @record(mut=True) diff --git a/python/test-data-2.0/extras/invalidRecord.out b/python/file-test-data/extras/invalidRecord.out similarity index 100% rename from python/test-data-2.0/extras/invalidRecord.out rename to python/file-test-data/extras/invalidRecord.out diff --git a/python/test-data-2.0/extras/invalidRecord.py b/python/file-test-data/extras/invalidRecord.py similarity index 100% rename from python/test-data-2.0/extras/invalidRecord.py rename to python/file-test-data/extras/invalidRecord.py diff --git a/python/test-data-2.0/extras/invalidType.err b/python/file-test-data/extras/invalidType.err similarity index 69% rename from python/test-data-2.0/extras/invalidType.err rename to python/file-test-data/extras/invalidType.err index b940bd19..739a7f86 100644 --- a/python/test-data-2.0/extras/invalidType.err +++ b/python/file-test-data/extras/invalidType.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/invalidType.py", line 9, in + 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 test-data-2.0/extras/invalidType.py +## Datei file-test-data/extras/invalidType.py ## Typ deklariert in Zeile 6: def foo() -> Union(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/extras/invalidType.out b/python/file-test-data/extras/invalidType.out similarity index 100% rename from python/test-data-2.0/extras/invalidType.out rename to python/file-test-data/extras/invalidType.out diff --git a/python/test-data-2.0/extras/invalidType.py b/python/file-test-data/extras/invalidType.py similarity index 100% rename from python/test-data-2.0/extras/invalidType.py rename to python/file-test-data/extras/invalidType.py diff --git a/python/test-data-2.0/extras/invalidType2.err b/python/file-test-data/extras/invalidType2.err similarity index 62% rename from python/test-data-2.0/extras/invalidType2.err rename to python/file-test-data/extras/invalidType2.err index 3da0f076..abf47854 100644 --- a/python/test-data-2.0/extras/invalidType2.err +++ b/python/file-test-data/extras/invalidType2.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/extras/invalidType2.py", line 5, in + 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-2.0/extras/invalidType2.out b/python/file-test-data/extras/invalidType2.out similarity index 100% rename from python/test-data-2.0/extras/invalidType2.out rename to python/file-test-data/extras/invalidType2.out diff --git a/python/test-data-2.0/extras/invalidType2.py b/python/file-test-data/extras/invalidType2.py similarity index 100% rename from python/test-data-2.0/extras/invalidType2.py rename to python/file-test-data/extras/invalidType2.py diff --git a/python/test-data-2.0/extras/invalidType3.err b/python/file-test-data/extras/invalidType3.err similarity index 69% rename from python/test-data-2.0/extras/invalidType3.err rename to python/file-test-data/extras/invalidType3.err index 119af10f..8a772f48 100644 --- a/python/test-data-2.0/extras/invalidType3.err +++ b/python/file-test-data/extras/invalidType3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/invalidType3.py", line 9, in + 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 test-data-2.0/extras/invalidType3.py +## Datei file-test-data/extras/invalidType3.py ## Typ deklariert in Zeile 6: def foo() -> Optional(list(int), list[float]): \ No newline at end of file diff --git a/python/test-data-2.0/extras/invalidType3.out b/python/file-test-data/extras/invalidType3.out similarity index 100% rename from python/test-data-2.0/extras/invalidType3.out rename to python/file-test-data/extras/invalidType3.out diff --git a/python/test-data-2.0/extras/invalidType3.py b/python/file-test-data/extras/invalidType3.py similarity index 100% rename from python/test-data-2.0/extras/invalidType3.py rename to python/file-test-data/extras/invalidType3.py diff --git a/python/test-data-2.0/extras/invalidType4.err b/python/file-test-data/extras/invalidType4.err similarity index 64% rename from python/test-data-2.0/extras/invalidType4.err rename to python/file-test-data/extras/invalidType4.err index 896b8ae2..b261d290 100644 --- a/python/test-data-2.0/extras/invalidType4.err +++ b/python/file-test-data/extras/invalidType4.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/extras/invalidType4.py", line 9, in + File "file-test-data/extras/invalidType4.py", line 9, in foo() WyppTypeError: ungültiger Typ `Optional[list[int], list[float]]` -## Datei test-data-2.0/extras/invalidType4.py +## Datei file-test-data/extras/invalidType4.py ## Typ deklariert in Zeile 6: def foo() -> Optional[list[int], list[float]]: \ No newline at end of file diff --git a/python/test-data-2.0/extras/invalidType4.out b/python/file-test-data/extras/invalidType4.out similarity index 100% rename from python/test-data-2.0/extras/invalidType4.out rename to python/file-test-data/extras/invalidType4.out diff --git a/python/test-data-2.0/extras/invalidType4.py b/python/file-test-data/extras/invalidType4.py similarity index 100% rename from python/test-data-2.0/extras/invalidType4.py rename to python/file-test-data/extras/invalidType4.py diff --git a/python/test-data-2.0/extras/main.err b/python/file-test-data/extras/main.err similarity index 69% rename from python/test-data-2.0/extras/main.err rename to python/file-test-data/extras/main.err index cb3f5dd2..4e925967 100644 --- a/python/test-data-2.0/extras/main.err +++ b/python/file-test-data/extras/main.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/main.py", line 4, in + File "file-test-data/extras/main.py", line 4, in print(mod.foo(1)) - File "test-data-2.0/modules/B/mod.py", line 5, in foo + File "file-test-data/modules/B/mod.py", line 5, in foo return bar(i) WyppTypeError: 1 @@ -9,7 +9,7 @@ WyppTypeError: 1 Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/modules/B/mod.py +## Datei file-test-data/modules/B/mod.py ## Fehlerhafter Aufruf in Zeile 5: return bar(i) diff --git a/python/test-data-2.0/extras/main.out b/python/file-test-data/extras/main.out similarity index 100% rename from python/test-data-2.0/extras/main.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-2.0/extras/printModuleNameImport_ok.err b/python/file-test-data/extras/printModuleNameImport_ok.err similarity index 100% rename from python/test-data-2.0/extras/printModuleNameImport_ok.err rename to python/file-test-data/extras/printModuleNameImport_ok.err diff --git a/python/test-data-2.0/extras/printModuleNameImport_ok.out b/python/file-test-data/extras/printModuleNameImport_ok.out similarity index 100% rename from python/test-data-2.0/extras/printModuleNameImport_ok.out rename to python/file-test-data/extras/printModuleNameImport_ok.out diff --git a/python/test-data-2.0/extras/printModuleNameImport_ok.py b/python/file-test-data/extras/printModuleNameImport_ok.py similarity index 100% rename from python/test-data-2.0/extras/printModuleNameImport_ok.py rename to python/file-test-data/extras/printModuleNameImport_ok.py diff --git a/python/test-data-2.0/extras/printModuleName_ok.err b/python/file-test-data/extras/printModuleName_ok.err similarity index 100% rename from python/test-data-2.0/extras/printModuleName_ok.err rename to python/file-test-data/extras/printModuleName_ok.err diff --git a/python/test-data-2.0/extras/printModuleName_ok.out b/python/file-test-data/extras/printModuleName_ok.out similarity index 100% rename from python/test-data-2.0/extras/printModuleName_ok.out rename to python/file-test-data/extras/printModuleName_ok.out diff --git a/python/test-data-2.0/extras/printModuleName_ok.py b/python/file-test-data/extras/printModuleName_ok.py similarity index 100% rename from python/test-data-2.0/extras/printModuleName_ok.py rename to python/file-test-data/extras/printModuleName_ok.py diff --git a/python/test-data-2.0/extras/testABCMeta.err b/python/file-test-data/extras/testABCMeta.err similarity index 70% rename from python/test-data-2.0/extras/testABCMeta.err rename to python/file-test-data/extras/testABCMeta.err index 1ed256d7..4ffca11e 100644 --- a/python/test-data-2.0/extras/testABCMeta.err +++ b/python/file-test-data/extras/testABCMeta.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/extras/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' \ No newline at end of file diff --git a/python/test-data-2.0/extras/testABCMeta.err-3.10 b/python/file-test-data/extras/testABCMeta.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testABCMeta.err-3.10 rename to python/file-test-data/extras/testABCMeta.err-3.10 diff --git a/python/test-data-2.0/extras/testABCMeta.err-3.11 b/python/file-test-data/extras/testABCMeta.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testABCMeta.err-3.11 rename to python/file-test-data/extras/testABCMeta.err-3.11 diff --git a/python/test-data-2.0/extras/testABCMeta.out b/python/file-test-data/extras/testABCMeta.out similarity index 100% rename from python/test-data-2.0/extras/testABCMeta.out rename to python/file-test-data/extras/testABCMeta.out diff --git a/python/test-data-2.0/extras/testABCMeta.py b/python/file-test-data/extras/testABCMeta.py similarity index 100% rename from python/test-data-2.0/extras/testABCMeta.py rename to python/file-test-data/extras/testABCMeta.py diff --git a/python/test-data-2.0/extras/testABC_ok.err b/python/file-test-data/extras/testABC_ok.err similarity index 100% rename from python/test-data-2.0/extras/testABC_ok.err rename to python/file-test-data/extras/testABC_ok.err diff --git a/python/test-data-2.0/extras/testABC_ok.out b/python/file-test-data/extras/testABC_ok.out similarity index 100% rename from python/test-data-2.0/extras/testABC_ok.out rename to python/file-test-data/extras/testABC_ok.out diff --git a/python/test-data-2.0/extras/testABC_ok.py b/python/file-test-data/extras/testABC_ok.py similarity index 100% rename from python/test-data-2.0/extras/testABC_ok.py rename to python/file-test-data/extras/testABC_ok.py diff --git a/python/test-data-2.0/extras/testArgs_ok.err b/python/file-test-data/extras/testArgs_ok.err similarity index 100% rename from python/test-data-2.0/extras/testArgs_ok.err 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/test-data-2.0/extras/testArgs_ok.py b/python/file-test-data/extras/testArgs_ok.py similarity index 100% rename from python/test-data-2.0/extras/testArgs_ok.py rename to python/file-test-data/extras/testArgs_ok.py diff --git a/python/test-data-2.0/extras/testBugSliceIndices_ok.err b/python/file-test-data/extras/testBugSliceIndices_ok.err similarity index 100% rename from python/test-data-2.0/extras/testBugSliceIndices_ok.err rename to python/file-test-data/extras/testBugSliceIndices_ok.err diff --git a/python/test-data-2.0/extras/testBugSliceIndices_ok.out b/python/file-test-data/extras/testBugSliceIndices_ok.out similarity index 100% rename from python/test-data-2.0/extras/testBugSliceIndices_ok.out rename to python/file-test-data/extras/testBugSliceIndices_ok.out diff --git a/python/test-data-2.0/extras/testBugSliceIndices_ok.py b/python/file-test-data/extras/testBugSliceIndices_ok.py similarity index 100% rename from python/test-data-2.0/extras/testBugSliceIndices_ok.py rename to python/file-test-data/extras/testBugSliceIndices_ok.py diff --git a/python/test-data-2.0/extras/testCheckFail_ok.err b/python/file-test-data/extras/testCheckFail_ok.err similarity index 100% rename from python/test-data-2.0/extras/testCheckFail_ok.err rename to python/file-test-data/extras/testCheckFail_ok.err diff --git a/python/test-data-2.0/extras/testCheckFail_ok.out b/python/file-test-data/extras/testCheckFail_ok.out similarity index 100% rename from python/test-data-2.0/extras/testCheckFail_ok.out rename to python/file-test-data/extras/testCheckFail_ok.out diff --git a/python/test-data-2.0/extras/testCheckFail_ok.py b/python/file-test-data/extras/testCheckFail_ok.py similarity index 100% rename from python/test-data-2.0/extras/testCheckFail_ok.py rename to python/file-test-data/extras/testCheckFail_ok.py diff --git a/python/test-data-2.0/extras/testCheck_ok.err b/python/file-test-data/extras/testCheck_ok.err similarity index 100% rename from python/test-data-2.0/extras/testCheck_ok.err 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..9be5c707 --- /dev/null +++ b/python/file-test-data/extras/testCheck_ok.out @@ -0,0 +1,5 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.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-2.0/extras/testCheck_ok.py b/python/file-test-data/extras/testCheck_ok.py similarity index 100% rename from python/test-data-2.0/extras/testCheck_ok.py rename to python/file-test-data/extras/testCheck_ok.py diff --git a/python/test-data-2.0/extras/testClassHierarchy_ok.err b/python/file-test-data/extras/testClassHierarchy_ok.err similarity index 100% rename from python/test-data-2.0/extras/testClassHierarchy_ok.err rename to python/file-test-data/extras/testClassHierarchy_ok.err diff --git a/python/test-data-2.0/extras/testClassHierarchy_ok.out b/python/file-test-data/extras/testClassHierarchy_ok.out similarity index 100% rename from python/test-data-2.0/extras/testClassHierarchy_ok.out rename to python/file-test-data/extras/testClassHierarchy_ok.out diff --git a/python/test-data-2.0/extras/testClassHierarchy_ok.py b/python/file-test-data/extras/testClassHierarchy_ok.py similarity index 100% rename from python/test-data-2.0/extras/testClassHierarchy_ok.py rename to python/file-test-data/extras/testClassHierarchy_ok.py diff --git a/python/test-data-2.0/extras/testClassRecursion_ok.err b/python/file-test-data/extras/testClassRecursion_ok.err similarity index 100% rename from python/test-data-2.0/extras/testClassRecursion_ok.err rename to python/file-test-data/extras/testClassRecursion_ok.err diff --git a/python/test-data-2.0/extras/testClassRecursion_ok.out b/python/file-test-data/extras/testClassRecursion_ok.out similarity index 100% rename from python/test-data-2.0/extras/testClassRecursion_ok.out rename to python/file-test-data/extras/testClassRecursion_ok.out diff --git a/python/test-data-2.0/extras/testClassRecursion_ok.py b/python/file-test-data/extras/testClassRecursion_ok.py similarity index 100% rename from python/test-data-2.0/extras/testClassRecursion_ok.py rename to python/file-test-data/extras/testClassRecursion_ok.py diff --git a/python/test-data-2.0/extras/testComplex_ok.err b/python/file-test-data/extras/testComplex_ok.err similarity index 100% rename from python/test-data-2.0/extras/testComplex_ok.err rename to python/file-test-data/extras/testComplex_ok.err diff --git a/python/test-data-2.0/extras/testComplex_ok.out b/python/file-test-data/extras/testComplex_ok.out similarity index 100% rename from python/test-data-2.0/extras/testComplex_ok.out rename to python/file-test-data/extras/testComplex_ok.out diff --git a/python/test-data-2.0/extras/testComplex_ok.py b/python/file-test-data/extras/testComplex_ok.py similarity index 100% rename from python/test-data-2.0/extras/testComplex_ok.py rename to python/file-test-data/extras/testComplex_ok.py diff --git a/python/test-data-2.0/extras/testConcat_ok.err b/python/file-test-data/extras/testConcat_ok.err similarity index 100% rename from python/test-data-2.0/extras/testConcat_ok.err rename to python/file-test-data/extras/testConcat_ok.err diff --git a/python/test-data-2.0/extras/testConcat_ok.out b/python/file-test-data/extras/testConcat_ok.out similarity index 100% rename from python/test-data-2.0/extras/testConcat_ok.out rename to python/file-test-data/extras/testConcat_ok.out diff --git a/python/test-data-2.0/extras/testConcat_ok.py b/python/file-test-data/extras/testConcat_ok.py similarity index 100% rename from python/test-data-2.0/extras/testConcat_ok.py rename to python/file-test-data/extras/testConcat_ok.py diff --git a/python/test-data-2.0/extras/testCopy_ok.err b/python/file-test-data/extras/testCopy_ok.err similarity index 100% rename from python/test-data-2.0/extras/testCopy_ok.err rename to python/file-test-data/extras/testCopy_ok.err diff --git a/python/test-data-2.0/extras/testCopy_ok.out b/python/file-test-data/extras/testCopy_ok.out similarity index 100% rename from python/test-data-2.0/extras/testCopy_ok.out rename to python/file-test-data/extras/testCopy_ok.out diff --git a/python/test-data-2.0/extras/testCopy_ok.py b/python/file-test-data/extras/testCopy_ok.py similarity index 100% rename from python/test-data-2.0/extras/testCopy_ok.py rename to python/file-test-data/extras/testCopy_ok.py diff --git a/python/test-data-2.0/extras/testDeepEqBug_ok.err b/python/file-test-data/extras/testDeepEqBug_ok.err similarity index 100% rename from python/test-data-2.0/extras/testDeepEqBug_ok.err 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..5f12c81c --- /dev/null +++ b/python/file-test-data/extras/testDeepEqBug_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testDeepEqBug_ok.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-2.0/extras/testDeepEqBug_ok.py b/python/file-test-data/extras/testDeepEqBug_ok.py similarity index 100% rename from python/test-data-2.0/extras/testDeepEqBug_ok.py rename to python/file-test-data/extras/testDeepEqBug_ok.py diff --git a/python/test-data-2.0/extras/testDict_ok.err b/python/file-test-data/extras/testDict_ok.err similarity index 100% rename from python/test-data-2.0/extras/testDict_ok.err rename to python/file-test-data/extras/testDict_ok.err diff --git a/python/test-data-2.0/extras/testDict_ok.out b/python/file-test-data/extras/testDict_ok.out similarity index 100% rename from python/test-data-2.0/extras/testDict_ok.out rename to python/file-test-data/extras/testDict_ok.out diff --git a/python/test-data-2.0/extras/testDict_ok.py b/python/file-test-data/extras/testDict_ok.py similarity index 100% rename from python/test-data-2.0/extras/testDict_ok.py rename to python/file-test-data/extras/testDict_ok.py diff --git a/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.err b/python/file-test-data/extras/testDoubleWrappingDicts_ok.err similarity index 100% rename from python/test-data-2.0/extras/testDoubleWrappingDicts_ok.err rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.err diff --git a/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.out b/python/file-test-data/extras/testDoubleWrappingDicts_ok.out similarity index 100% rename from python/test-data-2.0/extras/testDoubleWrappingDicts_ok.out rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.out diff --git a/python/test-data-2.0/extras/testDoubleWrappingDicts_ok.py b/python/file-test-data/extras/testDoubleWrappingDicts_ok.py similarity index 100% rename from python/test-data-2.0/extras/testDoubleWrappingDicts_ok.py rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.py diff --git a/python/test-data-2.0/extras/testForwardRef1_ok.err b/python/file-test-data/extras/testForwardRef1_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef1_ok.err rename to python/file-test-data/extras/testForwardRef1_ok.err diff --git a/python/test-data-2.0/extras/testForwardRef1_ok.out b/python/file-test-data/extras/testForwardRef1_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef1_ok.out rename to python/file-test-data/extras/testForwardRef1_ok.out diff --git a/python/test-data-2.0/extras/testForwardRef1_ok.py b/python/file-test-data/extras/testForwardRef1_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef1_ok.py rename to python/file-test-data/extras/testForwardRef1_ok.py diff --git a/python/test-data-2.0/extras/testForwardRef2.err b/python/file-test-data/extras/testForwardRef2.err similarity index 59% rename from python/test-data-2.0/extras/testForwardRef2.err rename to python/file-test-data/extras/testForwardRef2.err index 45795381..87cf3893 100644 --- a/python/test-data-2.0/extras/testForwardRef2.err +++ b/python/file-test-data/extras/testForwardRef2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testForwardRef2.py", line 10, in + File "file-test-data/extras/testForwardRef2.py", line 10, in t = Test(FooX()) WyppTypeError: ungültiger Typ `Foo` -## Datei test-data-2.0/extras/testForwardRef2.py +## Datei file-test-data/extras/testForwardRef2.py ## Typ deklariert in Zeile 2: def __init__(self, foo: 'Foo'): \ No newline at end of file diff --git a/python/test-data-2.0/extras/testForwardRef2.out b/python/file-test-data/extras/testForwardRef2.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef2.out rename to python/file-test-data/extras/testForwardRef2.out diff --git a/python/test-data-2.0/extras/testForwardRef2.py b/python/file-test-data/extras/testForwardRef2.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef2.py rename to python/file-test-data/extras/testForwardRef2.py diff --git a/python/test-data-2.0/extras/testForwardRef3_ok.err b/python/file-test-data/extras/testForwardRef3_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef3_ok.err rename to python/file-test-data/extras/testForwardRef3_ok.err diff --git a/python/test-data-2.0/extras/testForwardRef3_ok.out b/python/file-test-data/extras/testForwardRef3_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef3_ok.out rename to python/file-test-data/extras/testForwardRef3_ok.out diff --git a/python/test-data-2.0/extras/testForwardRef3_ok.py b/python/file-test-data/extras/testForwardRef3_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef3_ok.py rename to python/file-test-data/extras/testForwardRef3_ok.py diff --git a/python/test-data-2.0/extras/testForwardRef4.err b/python/file-test-data/extras/testForwardRef4.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef4.err rename to python/file-test-data/extras/testForwardRef4.err diff --git a/python/test-data-2.0/extras/testForwardRef4.err-3.10 b/python/file-test-data/extras/testForwardRef4.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testForwardRef4.err-3.10 rename to python/file-test-data/extras/testForwardRef4.err-3.10 diff --git a/python/test-data-2.0/extras/testForwardRef4.err-3.11 b/python/file-test-data/extras/testForwardRef4.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testForwardRef4.err-3.11 rename to python/file-test-data/extras/testForwardRef4.err-3.11 diff --git a/python/test-data-2.0/extras/testForwardRef4.err-3.12 b/python/file-test-data/extras/testForwardRef4.err-3.12 similarity index 56% rename from python/test-data-2.0/extras/testForwardRef4.err-3.12 rename to python/file-test-data/extras/testForwardRef4.err-3.12 index 85fe5efe..a2783f29 100644 --- a/python/test-data-2.0/extras/testForwardRef4.err-3.12 +++ b/python/file-test-data/extras/testForwardRef4.err-3.12 @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testForwardRef4.py", line 11, in + File "file-test-data/extras/testForwardRef4.py", line 11, in t = Test(Foo()) WyppTypeError: ungültiger Typ `FooX` -## Datei test-data-2.0/extras/testForwardRef4.py +## Datei file-test-data/extras/testForwardRef4.py ## Typ deklariert in Zeile 5: foo: 'FooX' \ No newline at end of file diff --git a/python/test-data-2.0/extras/testForwardRef4.out b/python/file-test-data/extras/testForwardRef4.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef4.out rename to python/file-test-data/extras/testForwardRef4.out diff --git a/python/test-data-2.0/extras/testForwardRef4.py b/python/file-test-data/extras/testForwardRef4.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef4.py rename to python/file-test-data/extras/testForwardRef4.py diff --git a/python/test-data-2.0/extras/testForwardRef5.err b/python/file-test-data/extras/testForwardRef5.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef5.err rename to python/file-test-data/extras/testForwardRef5.err diff --git a/python/test-data-2.0/extras/testForwardRef5.err-3.10 b/python/file-test-data/extras/testForwardRef5.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testForwardRef5.err-3.10 rename to python/file-test-data/extras/testForwardRef5.err-3.10 diff --git a/python/test-data-2.0/extras/testForwardRef5.err-3.11 b/python/file-test-data/extras/testForwardRef5.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testForwardRef5.err-3.11 rename to python/file-test-data/extras/testForwardRef5.err-3.11 diff --git a/python/test-data-2.0/extras/testForwardRef5.err-3.12 b/python/file-test-data/extras/testForwardRef5.err-3.12 similarity index 77% rename from python/test-data-2.0/extras/testForwardRef5.err-3.12 rename to python/file-test-data/extras/testForwardRef5.err-3.12 index fb1e0be5..c13aa2dd 100644 --- a/python/test-data-2.0/extras/testForwardRef5.err-3.12 +++ b/python/file-test-data/extras/testForwardRef5.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testForwardRef5.py", line 22, in + 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 test-data-2.0/extras/testForwardRef5.py +## Datei file-test-data/extras/testForwardRef5.py ## Fehlerhafter Aufruf in Zeile 22: garage = Garage(cars=[Car(color='red'), "Not A Car"]) diff --git a/python/test-data-2.0/extras/testForwardRef5.out b/python/file-test-data/extras/testForwardRef5.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef5.out rename to python/file-test-data/extras/testForwardRef5.out diff --git a/python/test-data-2.0/extras/testForwardRef5.py b/python/file-test-data/extras/testForwardRef5.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef5.py rename to python/file-test-data/extras/testForwardRef5.py diff --git a/python/test-data-2.0/extras/testForwardRef6_ok.err b/python/file-test-data/extras/testForwardRef6_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef6_ok.err rename to python/file-test-data/extras/testForwardRef6_ok.err diff --git a/python/test-data-2.0/extras/testForwardRef6_ok.out b/python/file-test-data/extras/testForwardRef6_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef6_ok.out rename to python/file-test-data/extras/testForwardRef6_ok.out diff --git a/python/test-data-2.0/extras/testForwardRef6_ok.py b/python/file-test-data/extras/testForwardRef6_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef6_ok.py rename to python/file-test-data/extras/testForwardRef6_ok.py diff --git a/python/test-data-2.0/extras/testForwardRef_ok.err b/python/file-test-data/extras/testForwardRef_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardRef_ok.err rename to python/file-test-data/extras/testForwardRef_ok.err diff --git a/python/test-data-2.0/extras/testForwardRef_ok.out b/python/file-test-data/extras/testForwardRef_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardRef_ok.out rename to python/file-test-data/extras/testForwardRef_ok.out diff --git a/python/test-data-2.0/extras/testForwardRef_ok.py b/python/file-test-data/extras/testForwardRef_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardRef_ok.py rename to python/file-test-data/extras/testForwardRef_ok.py diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.err b/python/file-test-data/extras/testForwardTypeInRecord2_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord2_ok.err rename to python/file-test-data/extras/testForwardTypeInRecord2_ok.err diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.out b/python/file-test-data/extras/testForwardTypeInRecord2_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord2_ok.out rename to python/file-test-data/extras/testForwardTypeInRecord2_ok.out diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord2_ok.py b/python/file-test-data/extras/testForwardTypeInRecord2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord2_ok.py rename to python/file-test-data/extras/testForwardTypeInRecord2_ok.py diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord_ok.err b/python/file-test-data/extras/testForwardTypeInRecord_ok.err similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord_ok.err rename to python/file-test-data/extras/testForwardTypeInRecord_ok.err diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord_ok.out b/python/file-test-data/extras/testForwardTypeInRecord_ok.out similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord_ok.out rename to python/file-test-data/extras/testForwardTypeInRecord_ok.out diff --git a/python/test-data-2.0/extras/testForwardTypeInRecord_ok.py b/python/file-test-data/extras/testForwardTypeInRecord_ok.py similarity index 100% rename from python/test-data-2.0/extras/testForwardTypeInRecord_ok.py rename to python/file-test-data/extras/testForwardTypeInRecord_ok.py diff --git a/python/test-data-2.0/extras/testFunEq_ok.err b/python/file-test-data/extras/testFunEq_ok.err similarity index 100% rename from python/test-data-2.0/extras/testFunEq_ok.err rename to python/file-test-data/extras/testFunEq_ok.err 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..5f621b79 --- /dev/null +++ b/python/file-test-data/extras/testFunEq_ok.out @@ -0,0 +1,2 @@ +FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist +1 Test, 1 Fehler 🙁 diff --git a/python/test-data-2.0/extras/testFunEq_ok.py b/python/file-test-data/extras/testFunEq_ok.py similarity index 100% rename from python/test-data-2.0/extras/testFunEq_ok.py rename to python/file-test-data/extras/testFunEq_ok.py diff --git a/python/test-data-2.0/extras/testGetSource.err b/python/file-test-data/extras/testGetSource.err similarity index 81% rename from python/test-data-2.0/extras/testGetSource.err rename to python/file-test-data/extras/testGetSource.err index e5d1e1e0..87d36f03 100644 --- a/python/test-data-2.0/extras/testGetSource.err +++ b/python/file-test-data/extras/testGetSource.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testGetSource.py", line 11, in + File "file-test-data/extras/testGetSource.py", line 11, in Art = Literal('klein','mittag') # <= problem is here File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) @@ -10,7 +10,7 @@ WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` Wolltest du `Literal['klein', 'mittag']` schreiben? -## Datei test-data-2.0/extras/testGetSource.py +## Datei file-test-data/extras/testGetSource.py ## Typ deklariert in Zeile 11: Art = Literal('klein','mittag') # <= problem is here \ No newline at end of file diff --git a/python/test-data-2.0/extras/testGetSource.out b/python/file-test-data/extras/testGetSource.out similarity index 100% rename from python/test-data-2.0/extras/testGetSource.out rename to python/file-test-data/extras/testGetSource.out diff --git a/python/test-data-2.0/extras/testGetSource.py b/python/file-test-data/extras/testGetSource.py similarity index 100% rename from python/test-data-2.0/extras/testGetSource.py rename to python/file-test-data/extras/testGetSource.py diff --git a/python/test-data-2.0/extras/testHintParentheses1.err b/python/file-test-data/extras/testHintParentheses1.err similarity index 62% rename from python/test-data-2.0/extras/testHintParentheses1.err rename to python/file-test-data/extras/testHintParentheses1.err index 3adea29c..7cf31405 100644 --- a/python/test-data-2.0/extras/testHintParentheses1.err +++ b/python/file-test-data/extras/testHintParentheses1.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testHintParentheses1.py", line 8, in + 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 test-data-2.0/extras/testHintParentheses1.py +## Datei file-test-data/extras/testHintParentheses1.py ## Typ deklariert in Zeile 5: def foo(l: list(int)) -> int: \ No newline at end of file diff --git a/python/test-data-2.0/extras/testHintParentheses1.out b/python/file-test-data/extras/testHintParentheses1.out similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses1.out rename to python/file-test-data/extras/testHintParentheses1.out diff --git a/python/test-data-2.0/extras/testHintParentheses1.py b/python/file-test-data/extras/testHintParentheses1.py similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses1.py rename to python/file-test-data/extras/testHintParentheses1.py diff --git a/python/test-data-2.0/extras/testHintParentheses2.err b/python/file-test-data/extras/testHintParentheses2.err similarity index 60% rename from python/test-data-2.0/extras/testHintParentheses2.err rename to python/file-test-data/extras/testHintParentheses2.err index 18944b1a..7f253075 100644 --- a/python/test-data-2.0/extras/testHintParentheses2.err +++ b/python/file-test-data/extras/testHintParentheses2.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testHintParentheses2.py", line 8, in + File "file-test-data/extras/testHintParentheses2.py", line 8, in foo(1, {}) WyppTypeError: ungültiger Typ `dict[1, list(int)]` -## Datei test-data-2.0/extras/testHintParentheses2.py +## Datei file-test-data/extras/testHintParentheses2.py ## Typ deklariert in Zeile 5: def foo(a: 'int', b: 'dict[1, list(int)]') -> int: \ No newline at end of file diff --git a/python/test-data-2.0/extras/testHintParentheses2.out b/python/file-test-data/extras/testHintParentheses2.out similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses2.out rename to python/file-test-data/extras/testHintParentheses2.out diff --git a/python/test-data-2.0/extras/testHintParentheses2.py b/python/file-test-data/extras/testHintParentheses2.py similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses2.py rename to python/file-test-data/extras/testHintParentheses2.py diff --git a/python/test-data-2.0/extras/testHintParentheses3.err b/python/file-test-data/extras/testHintParentheses3.err similarity index 62% rename from python/test-data-2.0/extras/testHintParentheses3.err rename to python/file-test-data/extras/testHintParentheses3.err index a7ab8ccf..e5d29b1b 100644 --- a/python/test-data-2.0/extras/testHintParentheses3.err +++ b/python/file-test-data/extras/testHintParentheses3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testHintParentheses3.py", line 9, in + 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 test-data-2.0/extras/testHintParentheses3.py +## Datei file-test-data/extras/testHintParentheses3.py ## Typ deklariert in Zeile 6: def foo() -> Union(list, str): \ No newline at end of file diff --git a/python/test-data-2.0/extras/testHintParentheses3.out b/python/file-test-data/extras/testHintParentheses3.out similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses3.out rename to python/file-test-data/extras/testHintParentheses3.out diff --git a/python/test-data-2.0/extras/testHintParentheses3.py b/python/file-test-data/extras/testHintParentheses3.py similarity index 100% rename from python/test-data-2.0/extras/testHintParentheses3.py rename to python/file-test-data/extras/testHintParentheses3.py diff --git a/python/test-data-2.0/extras/testHof_ok.err b/python/file-test-data/extras/testHof_ok.err similarity index 100% rename from python/test-data-2.0/extras/testHof_ok.err rename to python/file-test-data/extras/testHof_ok.err diff --git a/python/test-data-2.0/extras/testHof_ok.out b/python/file-test-data/extras/testHof_ok.out similarity index 100% rename from python/test-data-2.0/extras/testHof_ok.out rename to python/file-test-data/extras/testHof_ok.out diff --git a/python/test-data-2.0/extras/testHof_ok.py b/python/file-test-data/extras/testHof_ok.py similarity index 100% rename from python/test-data-2.0/extras/testHof_ok.py rename to python/file-test-data/extras/testHof_ok.py diff --git a/python/test-data-2.0/extras/testImpossible.err b/python/file-test-data/extras/testImpossible.err similarity index 72% rename from python/test-data-2.0/extras/testImpossible.err rename to python/file-test-data/extras/testImpossible.err index 43365f4a..a85724f2 100644 --- a/python/test-data-2.0/extras/testImpossible.err +++ b/python/file-test-data/extras/testImpossible.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testImpossible.py", line 3, in + File "file-test-data/extras/testImpossible.py", line 3, in impossible() File "site-lib/wypp/writeYourProgram.py", line 331, in impossible raise errors.ImpossibleError(msg) diff --git a/python/test-data-2.0/extras/testImpossible.out b/python/file-test-data/extras/testImpossible.out similarity index 100% rename from python/test-data-2.0/extras/testImpossible.out rename to python/file-test-data/extras/testImpossible.out diff --git a/python/test-data-2.0/extras/testImpossible.py b/python/file-test-data/extras/testImpossible.py similarity index 100% rename from python/test-data-2.0/extras/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/test-data-2.0/extras/testIndexError.out b/python/file-test-data/extras/testIndexError.out similarity index 100% rename from python/test-data-2.0/extras/testIndexError.out rename to python/file-test-data/extras/testIndexError.out diff --git a/python/test-data-2.0/extras/testIndexError.py b/python/file-test-data/extras/testIndexError.py similarity index 100% rename from python/test-data-2.0/extras/testIndexError.py rename to python/file-test-data/extras/testIndexError.py diff --git a/python/test-data-2.0/extras/testIndexSeq_ok.err b/python/file-test-data/extras/testIndexSeq_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIndexSeq_ok.err rename to python/file-test-data/extras/testIndexSeq_ok.err diff --git a/python/test-data-2.0/extras/testIndexSeq_ok.out b/python/file-test-data/extras/testIndexSeq_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIndexSeq_ok.out rename to python/file-test-data/extras/testIndexSeq_ok.out diff --git a/python/test-data-2.0/extras/testIndexSeq_ok.py b/python/file-test-data/extras/testIndexSeq_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIndexSeq_ok.py rename to python/file-test-data/extras/testIndexSeq_ok.py diff --git a/python/test-data-2.0/extras/testInvalidLiteral.err b/python/file-test-data/extras/testInvalidLiteral.err similarity index 60% rename from python/test-data-2.0/extras/testInvalidLiteral.err rename to python/file-test-data/extras/testInvalidLiteral.err index 62e84749..85d327ec 100644 --- a/python/test-data-2.0/extras/testInvalidLiteral.err +++ b/python/file-test-data/extras/testInvalidLiteral.err @@ -1,10 +1,10 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testInvalidLiteral.py", line 8, in + File "file-test-data/extras/testInvalidLiteral.py", line 8, in gameFull([['x']]) WyppTypeError: ungültiger Typ `list[list[['x', 'o', '-']]]` -## Datei test-data-2.0/extras/testInvalidLiteral.py +## Datei file-test-data/extras/testInvalidLiteral.py ## Typ deklariert in Zeile 5: def gameFull(game:Game)->bool: \ No newline at end of file diff --git a/python/test-data-2.0/extras/testInvalidLiteral.out b/python/file-test-data/extras/testInvalidLiteral.out similarity index 100% rename from python/test-data-2.0/extras/testInvalidLiteral.out rename to python/file-test-data/extras/testInvalidLiteral.out diff --git a/python/test-data-2.0/extras/testInvalidLiteral.py b/python/file-test-data/extras/testInvalidLiteral.py similarity index 100% rename from python/test-data-2.0/extras/testInvalidLiteral.py rename to python/file-test-data/extras/testInvalidLiteral.py diff --git a/python/test-data-2.0/extras/testIterable1_ok.err b/python/file-test-data/extras/testIterable1_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable1_ok.err rename to python/file-test-data/extras/testIterable1_ok.err diff --git a/python/test-data-2.0/extras/testIterable1_ok.out b/python/file-test-data/extras/testIterable1_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable1_ok.out rename to python/file-test-data/extras/testIterable1_ok.out diff --git a/python/test-data-2.0/extras/testIterable1_ok.py b/python/file-test-data/extras/testIterable1_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable1_ok.py rename to python/file-test-data/extras/testIterable1_ok.py diff --git a/python/test-data-2.0/extras/testIterable2_ok.err b/python/file-test-data/extras/testIterable2_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable2_ok.err rename to python/file-test-data/extras/testIterable2_ok.err diff --git a/python/test-data-2.0/extras/testIterable2_ok.out b/python/file-test-data/extras/testIterable2_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable2_ok.out rename to python/file-test-data/extras/testIterable2_ok.out diff --git a/python/test-data-2.0/extras/testIterable2_ok.py b/python/file-test-data/extras/testIterable2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable2_ok.py rename to python/file-test-data/extras/testIterable2_ok.py diff --git a/python/test-data-2.0/extras/testIterable3_ok.err b/python/file-test-data/extras/testIterable3_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable3_ok.err rename to python/file-test-data/extras/testIterable3_ok.err diff --git a/python/test-data-2.0/extras/testIterable3_ok.out b/python/file-test-data/extras/testIterable3_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable3_ok.out rename to python/file-test-data/extras/testIterable3_ok.out diff --git a/python/test-data-2.0/extras/testIterable3_ok.py b/python/file-test-data/extras/testIterable3_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable3_ok.py rename to python/file-test-data/extras/testIterable3_ok.py diff --git a/python/test-data-2.0/extras/testIterable4_ok.err b/python/file-test-data/extras/testIterable4_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable4_ok.err rename to python/file-test-data/extras/testIterable4_ok.err diff --git a/python/test-data-2.0/extras/testIterable4_ok.out b/python/file-test-data/extras/testIterable4_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable4_ok.out rename to python/file-test-data/extras/testIterable4_ok.out diff --git a/python/test-data-2.0/extras/testIterable4_ok.py b/python/file-test-data/extras/testIterable4_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable4_ok.py rename to python/file-test-data/extras/testIterable4_ok.py diff --git a/python/test-data-2.0/extras/testIterable5_ok.err b/python/file-test-data/extras/testIterable5_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable5_ok.err rename to python/file-test-data/extras/testIterable5_ok.err diff --git a/python/test-data-2.0/extras/testIterable5_ok.out b/python/file-test-data/extras/testIterable5_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable5_ok.out rename to python/file-test-data/extras/testIterable5_ok.out diff --git a/python/test-data-2.0/extras/testIterable5_ok.py b/python/file-test-data/extras/testIterable5_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable5_ok.py rename to python/file-test-data/extras/testIterable5_ok.py diff --git a/python/test-data-2.0/extras/testIterable6_ok.err b/python/file-test-data/extras/testIterable6_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable6_ok.err rename to python/file-test-data/extras/testIterable6_ok.err diff --git a/python/test-data-2.0/extras/testIterable6_ok.out b/python/file-test-data/extras/testIterable6_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable6_ok.out rename to python/file-test-data/extras/testIterable6_ok.out diff --git a/python/test-data-2.0/extras/testIterable6_ok.py b/python/file-test-data/extras/testIterable6_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable6_ok.py rename to python/file-test-data/extras/testIterable6_ok.py diff --git a/python/test-data-2.0/extras/testIterable7_ok.err b/python/file-test-data/extras/testIterable7_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterable7_ok.err rename to python/file-test-data/extras/testIterable7_ok.err diff --git a/python/test-data-2.0/extras/testIterable7_ok.out b/python/file-test-data/extras/testIterable7_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterable7_ok.out rename to python/file-test-data/extras/testIterable7_ok.out diff --git a/python/test-data-2.0/extras/testIterable7_ok.py b/python/file-test-data/extras/testIterable7_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterable7_ok.py rename to python/file-test-data/extras/testIterable7_ok.py diff --git a/python/test-data-2.0/extras/testIterableImplicitAny.err b/python/file-test-data/extras/testIterableImplicitAny.err similarity index 72% rename from python/test-data-2.0/extras/testIterableImplicitAny.err rename to python/file-test-data/extras/testIterableImplicitAny.err index a405e4c2..33552986 100644 --- a/python/test-data-2.0/extras/testIterableImplicitAny.err +++ b/python/file-test-data/extras/testIterableImplicitAny.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testIterableImplicitAny.py", line 13, in + File "file-test-data/extras/testIterableImplicitAny.py", line 13, in foo(NotIterable()) WyppTypeError: NotIterable @@ -7,7 +7,7 @@ WyppTypeError: NotIterable Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Iterable` als erstes Argument. Aber der übergebene Wert hat Typ `NotIterable`. -## Datei test-data-2.0/extras/testIterableImplicitAny.py +## Datei file-test-data/extras/testIterableImplicitAny.py ## Fehlerhafter Aufruf in Zeile 13: foo(NotIterable()) diff --git a/python/test-data-2.0/extras/testIterableImplicitAny.out b/python/file-test-data/extras/testIterableImplicitAny.out similarity index 100% rename from python/test-data-2.0/extras/testIterableImplicitAny.out rename to python/file-test-data/extras/testIterableImplicitAny.out diff --git a/python/test-data-2.0/extras/testIterableImplicitAny.py b/python/file-test-data/extras/testIterableImplicitAny.py similarity index 100% rename from python/test-data-2.0/extras/testIterableImplicitAny.py rename to python/file-test-data/extras/testIterableImplicitAny.py diff --git a/python/test-data-2.0/extras/testIterator2_ok.err b/python/file-test-data/extras/testIterator2_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterator2_ok.err rename to python/file-test-data/extras/testIterator2_ok.err diff --git a/python/test-data-2.0/extras/testIterator2_ok.out b/python/file-test-data/extras/testIterator2_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterator2_ok.out rename to python/file-test-data/extras/testIterator2_ok.out diff --git a/python/test-data-2.0/extras/testIterator2_ok.py b/python/file-test-data/extras/testIterator2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterator2_ok.py rename to python/file-test-data/extras/testIterator2_ok.py diff --git a/python/test-data-2.0/extras/testIterator3_ok.err b/python/file-test-data/extras/testIterator3_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterator3_ok.err rename to python/file-test-data/extras/testIterator3_ok.err diff --git a/python/test-data-2.0/extras/testIterator3_ok.out b/python/file-test-data/extras/testIterator3_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterator3_ok.out rename to python/file-test-data/extras/testIterator3_ok.out diff --git a/python/test-data-2.0/extras/testIterator3_ok.py b/python/file-test-data/extras/testIterator3_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterator3_ok.py rename to python/file-test-data/extras/testIterator3_ok.py diff --git a/python/test-data-2.0/extras/testIterator4_ok.err b/python/file-test-data/extras/testIterator4_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterator4_ok.err rename to python/file-test-data/extras/testIterator4_ok.err diff --git a/python/test-data-2.0/extras/testIterator4_ok.out b/python/file-test-data/extras/testIterator4_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterator4_ok.out rename to python/file-test-data/extras/testIterator4_ok.out diff --git a/python/test-data-2.0/extras/testIterator4_ok.py b/python/file-test-data/extras/testIterator4_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterator4_ok.py rename to python/file-test-data/extras/testIterator4_ok.py diff --git a/python/test-data-2.0/extras/testIterator_ok.err b/python/file-test-data/extras/testIterator_ok.err similarity index 100% rename from python/test-data-2.0/extras/testIterator_ok.err rename to python/file-test-data/extras/testIterator_ok.err diff --git a/python/test-data-2.0/extras/testIterator_ok.out b/python/file-test-data/extras/testIterator_ok.out similarity index 100% rename from python/test-data-2.0/extras/testIterator_ok.out rename to python/file-test-data/extras/testIterator_ok.out diff --git a/python/test-data-2.0/extras/testIterator_ok.py b/python/file-test-data/extras/testIterator_ok.py similarity index 100% rename from python/test-data-2.0/extras/testIterator_ok.py rename to python/file-test-data/extras/testIterator_ok.py diff --git a/python/test-data-2.0/extras/testLiteral1.err b/python/file-test-data/extras/testLiteral1.err similarity index 79% rename from python/test-data-2.0/extras/testLiteral1.err rename to python/file-test-data/extras/testLiteral1.err index 0185b9b7..82e16bd8 100644 --- a/python/test-data-2.0/extras/testLiteral1.err +++ b/python/file-test-data/extras/testLiteral1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testLiteral1.py", line 3, in + File "file-test-data/extras/testLiteral1.py", line 3, in T = Literal('a', 'b') File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) @@ -10,7 +10,7 @@ WyppTypeError: ungültiger Typ `Literal('a', 'b')` Wolltest du `Literal['a', 'b']` schreiben? -## Datei test-data-2.0/extras/testLiteral1.py +## Datei file-test-data/extras/testLiteral1.py ## Typ deklariert in Zeile 3: T = Literal('a', 'b') \ No newline at end of file diff --git a/python/test-data-2.0/extras/testLiteral1.out b/python/file-test-data/extras/testLiteral1.out similarity index 100% rename from python/test-data-2.0/extras/testLiteral1.out rename to python/file-test-data/extras/testLiteral1.out diff --git a/python/test-data-2.0/extras/testLiteral1.py b/python/file-test-data/extras/testLiteral1.py similarity index 100% rename from python/test-data-2.0/extras/testLiteral1.py rename to python/file-test-data/extras/testLiteral1.py diff --git a/python/test-data-2.0/extras/testLiteralInstanceOf_ok.err b/python/file-test-data/extras/testLiteralInstanceOf_ok.err similarity index 100% rename from python/test-data-2.0/extras/testLiteralInstanceOf_ok.err rename to python/file-test-data/extras/testLiteralInstanceOf_ok.err diff --git a/python/test-data-2.0/extras/testLiteralInstanceOf_ok.out b/python/file-test-data/extras/testLiteralInstanceOf_ok.out similarity index 100% rename from python/test-data-2.0/extras/testLiteralInstanceOf_ok.out rename to python/file-test-data/extras/testLiteralInstanceOf_ok.out diff --git a/python/test-data-2.0/extras/testLiteralInstanceOf_ok.py b/python/file-test-data/extras/testLiteralInstanceOf_ok.py similarity index 100% rename from python/test-data-2.0/extras/testLiteralInstanceOf_ok.py rename to python/file-test-data/extras/testLiteralInstanceOf_ok.py diff --git a/python/test-data-2.0/extras/testLockFactory.err b/python/file-test-data/extras/testLockFactory.err similarity index 74% rename from python/test-data-2.0/extras/testLockFactory.err rename to python/file-test-data/extras/testLockFactory.err index 039488f7..933f618c 100644 --- a/python/test-data-2.0/extras/testLockFactory.err +++ b/python/file-test-data/extras/testLockFactory.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testLockFactory.py", line 15, in + File "file-test-data/extras/testLockFactory.py", line 15, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/testLockFactory.py +## Datei file-test-data/extras/testLockFactory.py ## Fehlerhafter Aufruf in Zeile 15: foo("not a lock") diff --git a/python/test-data-2.0/extras/testLockFactory.out b/python/file-test-data/extras/testLockFactory.out similarity index 100% rename from python/test-data-2.0/extras/testLockFactory.out rename to python/file-test-data/extras/testLockFactory.out diff --git a/python/test-data-2.0/extras/testLockFactory.py b/python/file-test-data/extras/testLockFactory.py similarity index 100% rename from python/test-data-2.0/extras/testLockFactory.py rename to python/file-test-data/extras/testLockFactory.py diff --git a/python/test-data-2.0/extras/testLockFactory2.err b/python/file-test-data/extras/testLockFactory2.err similarity index 73% rename from python/test-data-2.0/extras/testLockFactory2.err rename to python/file-test-data/extras/testLockFactory2.err index ac81c27e..be3a9241 100644 --- a/python/test-data-2.0/extras/testLockFactory2.err +++ b/python/file-test-data/extras/testLockFactory2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testLockFactory2.py", line 14, in + File "file-test-data/extras/testLockFactory2.py", line 14, in foo("not a lock") WyppTypeError: "not a lock" @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/testLockFactory2.py +## Datei file-test-data/extras/testLockFactory2.py ## Fehlerhafter Aufruf in Zeile 14: foo("not a lock") diff --git a/python/test-data-2.0/extras/testLockFactory2.out b/python/file-test-data/extras/testLockFactory2.out similarity index 100% rename from python/test-data-2.0/extras/testLockFactory2.out rename to python/file-test-data/extras/testLockFactory2.out diff --git a/python/test-data-2.0/extras/testLockFactory2.py b/python/file-test-data/extras/testLockFactory2.py similarity index 100% rename from python/test-data-2.0/extras/testLockFactory2.py rename to python/file-test-data/extras/testLockFactory2.py diff --git a/python/test-data-2.0/extras/testLockFactory_ok.err b/python/file-test-data/extras/testLockFactory_ok.err similarity index 100% rename from python/test-data-2.0/extras/testLockFactory_ok.err rename to python/file-test-data/extras/testLockFactory_ok.err diff --git a/python/test-data-2.0/extras/testLockFactory_ok.out b/python/file-test-data/extras/testLockFactory_ok.out similarity index 100% rename from python/test-data-2.0/extras/testLockFactory_ok.out rename to python/file-test-data/extras/testLockFactory_ok.out diff --git a/python/test-data-2.0/extras/testLockFactory_ok.py b/python/file-test-data/extras/testLockFactory_ok.py similarity index 100% rename from python/test-data-2.0/extras/testLockFactory_ok.py rename to python/file-test-data/extras/testLockFactory_ok.py diff --git a/python/test-data-2.0/extras/testMissingReturn.err b/python/file-test-data/extras/testMissingReturn.err similarity index 68% rename from python/test-data-2.0/extras/testMissingReturn.err rename to python/file-test-data/extras/testMissingReturn.err index 4551c2b1..14babc70 100644 --- a/python/test-data-2.0/extras/testMissingReturn.err +++ b/python/file-test-data/extras/testMissingReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testMissingReturn.py", line 6, in + File "file-test-data/extras/testMissingReturn.py", line 6, in print(billigStrom(500)) - File "test-data-2.0/extras/testMissingReturn.py", line 4, in billigStrom + File "file-test-data/extras/testMissingReturn.py", line 4, in billigStrom pass WyppTypeError: kein Rückgabewert vorhanden @@ -9,7 +9,7 @@ WyppTypeError: kein Rückgabewert vorhanden Rückgabewert vom Typ `float` erwartet bei Aufruf der Funktion `billigStrom`. Aber kein Rückgabewert vorhanden. -## Datei test-data-2.0/extras/testMissingReturn.py +## Datei file-test-data/extras/testMissingReturn.py ## Rückgabetyp deklariert in Zeile 3: def billigStrom(kwh: float) -> float: diff --git a/python/test-data-2.0/extras/testMissingReturn.out b/python/file-test-data/extras/testMissingReturn.out similarity index 100% rename from python/test-data-2.0/extras/testMissingReturn.out rename to python/file-test-data/extras/testMissingReturn.out diff --git a/python/test-data-2.0/extras/testMissingReturn.py b/python/file-test-data/extras/testMissingReturn.py similarity index 100% rename from python/test-data-2.0/extras/testMissingReturn.py rename to python/file-test-data/extras/testMissingReturn.py diff --git a/python/test-data-2.0/extras/testNameErrorBug_ok.err b/python/file-test-data/extras/testNameErrorBug_ok.err similarity index 100% rename from python/test-data-2.0/extras/testNameErrorBug_ok.err rename to python/file-test-data/extras/testNameErrorBug_ok.err diff --git a/python/test-data-2.0/extras/testNameErrorBug_ok.out b/python/file-test-data/extras/testNameErrorBug_ok.out similarity index 100% rename from python/test-data-2.0/extras/testNameErrorBug_ok.out rename to python/file-test-data/extras/testNameErrorBug_ok.out diff --git a/python/test-data-2.0/extras/testNameErrorBug_ok.py b/python/file-test-data/extras/testNameErrorBug_ok.py similarity index 100% rename from python/test-data-2.0/extras/testNameErrorBug_ok.py rename to python/file-test-data/extras/testNameErrorBug_ok.py diff --git a/python/test-data-2.0/extras/testOriginalTypeNames_ok.err b/python/file-test-data/extras/testOriginalTypeNames_ok.err similarity index 100% rename from python/test-data-2.0/extras/testOriginalTypeNames_ok.err rename to python/file-test-data/extras/testOriginalTypeNames_ok.err diff --git a/python/test-data-2.0/extras/testOriginalTypeNames_ok.out b/python/file-test-data/extras/testOriginalTypeNames_ok.out similarity index 100% rename from python/test-data-2.0/extras/testOriginalTypeNames_ok.out rename to python/file-test-data/extras/testOriginalTypeNames_ok.out diff --git a/python/test-data-2.0/extras/testOriginalTypeNames_ok.py b/python/file-test-data/extras/testOriginalTypeNames_ok.py similarity index 100% rename from python/test-data-2.0/extras/testOriginalTypeNames_ok.py rename to python/file-test-data/extras/testOriginalTypeNames_ok.py diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err b/python/file-test-data/extras/testRecordSetTypeForwardRef.err similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.err rename to python/file-test-data/extras/testRecordSetTypeForwardRef.err diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.10 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.10 rename to python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.10 diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.11 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.11 rename to python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.11 diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 similarity index 60% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 rename to python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 index 30e21a6c..a4b6d328 100644 --- a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.err-3.12 +++ b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testRecordSetTypeForwardRef.py", line 15, in + File "file-test-data/extras/testRecordSetTypeForwardRef.py", line 15, in m() - File "test-data-2.0/extras/testRecordSetTypeForwardRef.py", line 13, in m + File "file-test-data/extras/testRecordSetTypeForwardRef.py", line 13, in m r.x = "hello" WyppTypeError: "hello" @@ -9,7 +9,7 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `A`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei test-data-2.0/extras/testRecordSetTypeForwardRef.py +## Datei file-test-data/extras/testRecordSetTypeForwardRef.py ## Fehlerhafte Zuweisung in Zeile 13: r.x = "hello" diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.out b/python/file-test-data/extras/testRecordSetTypeForwardRef.out similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.out rename to python/file-test-data/extras/testRecordSetTypeForwardRef.out diff --git a/python/test-data-2.0/extras/testRecordSetTypeForwardRef.py b/python/file-test-data/extras/testRecordSetTypeForwardRef.py similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypeForwardRef.py rename to python/file-test-data/extras/testRecordSetTypeForwardRef.py diff --git a/python/test-data-2.0/extras/testRecordSetTypes.err b/python/file-test-data/extras/testRecordSetTypes.err similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypes.err rename to python/file-test-data/extras/testRecordSetTypes.err diff --git a/python/test-data-2.0/extras/testRecordSetTypes.err-3.10 b/python/file-test-data/extras/testRecordSetTypes.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypes.err-3.10 rename to python/file-test-data/extras/testRecordSetTypes.err-3.10 diff --git a/python/test-data-2.0/extras/testRecordSetTypes.err-3.11 b/python/file-test-data/extras/testRecordSetTypes.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypes.err-3.11 rename to python/file-test-data/extras/testRecordSetTypes.err-3.11 diff --git a/python/test-data-2.0/extras/testRecordSetTypes.err-3.12 b/python/file-test-data/extras/testRecordSetTypes.err-3.12 similarity index 64% rename from python/test-data-2.0/extras/testRecordSetTypes.err-3.12 rename to python/file-test-data/extras/testRecordSetTypes.err-3.12 index a1dea348..ce400994 100644 --- a/python/test-data-2.0/extras/testRecordSetTypes.err-3.12 +++ b/python/file-test-data/extras/testRecordSetTypes.err-3.12 @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testRecordSetTypes.py", line 12, in + File "file-test-data/extras/testRecordSetTypes.py", line 12, in m() - File "test-data-2.0/extras/testRecordSetTypes.py", line 10, in m + File "file-test-data/extras/testRecordSetTypes.py", line 10, in m r.x = "hello" WyppTypeError: "hello" @@ -9,7 +9,7 @@ WyppTypeError: "hello" Attribut `x` des Records `Record` deklariert als Typ `int`. Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden. -## Datei test-data-2.0/extras/testRecordSetTypes.py +## Datei file-test-data/extras/testRecordSetTypes.py ## Fehlerhafte Zuweisung in Zeile 10: r.x = "hello" diff --git a/python/test-data-2.0/extras/testRecordSetTypes.out b/python/file-test-data/extras/testRecordSetTypes.out similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypes.out rename to python/file-test-data/extras/testRecordSetTypes.out diff --git a/python/test-data-2.0/extras/testRecordSetTypes.py b/python/file-test-data/extras/testRecordSetTypes.py similarity index 100% rename from python/test-data-2.0/extras/testRecordSetTypes.py rename to python/file-test-data/extras/testRecordSetTypes.py diff --git a/python/test-data-2.0/extras/testRecordTypes.err b/python/file-test-data/extras/testRecordTypes.err similarity index 100% rename from python/test-data-2.0/extras/testRecordTypes.err rename to python/file-test-data/extras/testRecordTypes.err diff --git a/python/test-data-2.0/extras/testRecordTypes.err-3.12 b/python/file-test-data/extras/testRecordTypes.err-3.12 similarity index 74% rename from python/test-data-2.0/extras/testRecordTypes.err-3.12 rename to python/file-test-data/extras/testRecordTypes.err-3.12 index 7e9f2b03..aac97f56 100644 --- a/python/test-data-2.0/extras/testRecordTypes.err-3.12 +++ b/python/file-test-data/extras/testRecordTypes.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testRecordTypes.py", line 8, in + File "file-test-data/extras/testRecordTypes.py", line 8, in p = Point(1, '5') WyppTypeError: '5' @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/testRecordTypes.py +## Datei file-test-data/extras/testRecordTypes.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(1, '5') diff --git a/python/test-data-2.0/extras/testRecordTypes.out b/python/file-test-data/extras/testRecordTypes.out similarity index 100% rename from python/test-data-2.0/extras/testRecordTypes.out rename to python/file-test-data/extras/testRecordTypes.out diff --git a/python/test-data-2.0/extras/testRecordTypes.py b/python/file-test-data/extras/testRecordTypes.py similarity index 100% rename from python/test-data-2.0/extras/testRecordTypes.py rename to python/file-test-data/extras/testRecordTypes.py diff --git a/python/test-data-2.0/extras/testTodo.err b/python/file-test-data/extras/testTodo.err similarity index 69% rename from python/test-data-2.0/extras/testTodo.err rename to python/file-test-data/extras/testTodo.err index 17d4c7d0..8d2a43de 100644 --- a/python/test-data-2.0/extras/testTodo.err +++ b/python/file-test-data/extras/testTodo.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTodo.py", line 3, in + File "file-test-data/extras/testTodo.py", line 3, in todo() File "site-lib/wypp/writeYourProgram.py", line 326, in todo raise errors.TodoError(msg) diff --git a/python/test-data-2.0/extras/testTodo.out b/python/file-test-data/extras/testTodo.out similarity index 100% rename from python/test-data-2.0/extras/testTodo.out rename to python/file-test-data/extras/testTodo.out diff --git a/python/test-data-2.0/extras/testTodo.py b/python/file-test-data/extras/testTodo.py similarity index 100% rename from python/test-data-2.0/extras/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-2.0/extras/testTraceback.out b/python/file-test-data/extras/testTraceback.out similarity index 100% rename from python/test-data-2.0/extras/testTraceback.out rename to python/file-test-data/extras/testTraceback.out diff --git a/python/test-data-2.0/extras/testTraceback.py b/python/file-test-data/extras/testTraceback.py similarity index 100% rename from python/test-data-2.0/extras/testTraceback.py rename to python/file-test-data/extras/testTraceback.py diff --git a/python/file-test-data/extras/testTraceback2.err b/python/file-test-data/extras/testTraceback2.err new file mode 100644 index 00000000..db8fd026 --- /dev/null +++ b/python/file-test-data/extras/testTraceback2.err @@ -0,0 +1,4 @@ +File "/Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testTraceback2.py", line 3 + lst = [1,2,3 + ^ +SyntaxError: '[' was never closed \ No newline at end of file diff --git a/python/test-data-2.0/extras/testTraceback2.err-3.10.0 b/python/file-test-data/extras/testTraceback2.err-3.10.0 similarity index 100% rename from python/test-data-2.0/extras/testTraceback2.err-3.10.0 rename to python/file-test-data/extras/testTraceback2.err-3.10.0 diff --git a/python/test-data-2.0/extras/testTraceback2.err-3.9 b/python/file-test-data/extras/testTraceback2.err-3.9 similarity index 100% rename from python/test-data-2.0/extras/testTraceback2.err-3.9 rename to python/file-test-data/extras/testTraceback2.err-3.9 diff --git a/python/test-data-2.0/extras/testTraceback2.out b/python/file-test-data/extras/testTraceback2.out similarity index 100% rename from python/test-data-2.0/extras/testTraceback2.out rename to python/file-test-data/extras/testTraceback2.out diff --git a/python/test-data-2.0/extras/testTraceback2.py b/python/file-test-data/extras/testTraceback2.py similarity index 100% rename from python/test-data-2.0/extras/testTraceback2.py rename to python/file-test-data/extras/testTraceback2.py diff --git a/python/test-data-2.0/extras/testTraceback3.err b/python/file-test-data/extras/testTraceback3.err similarity index 57% rename from python/test-data-2.0/extras/testTraceback3.err rename to python/file-test-data/extras/testTraceback3.err index 4170a7a7..7bc652e2 100644 --- a/python/test-data-2.0/extras/testTraceback3.err +++ b/python/file-test-data/extras/testTraceback3.err @@ -1,4 +1,4 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTraceback3.py", line 2, in + 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/test-data-2.0/extras/testTraceback3.out b/python/file-test-data/extras/testTraceback3.out similarity index 100% rename from python/test-data-2.0/extras/testTraceback3.out rename to python/file-test-data/extras/testTraceback3.out diff --git a/python/test-data-2.0/extras/testTraceback3.py b/python/file-test-data/extras/testTraceback3.py similarity index 100% rename from python/test-data-2.0/extras/testTraceback3.py rename to python/file-test-data/extras/testTraceback3.py diff --git a/python/test-data-2.0/extras/testTypeKeyword_ok.err b/python/file-test-data/extras/testTypeKeyword_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypeKeyword_ok.err rename to python/file-test-data/extras/testTypeKeyword_ok.err diff --git a/python/test-data-2.0/extras/testTypeKeyword_ok.out b/python/file-test-data/extras/testTypeKeyword_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypeKeyword_ok.out rename to python/file-test-data/extras/testTypeKeyword_ok.out diff --git a/python/test-data-2.0/extras/testTypeKeyword_ok.py b/python/file-test-data/extras/testTypeKeyword_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypeKeyword_ok.py rename to python/file-test-data/extras/testTypeKeyword_ok.py diff --git a/python/test-data-2.0/extras/testTypes1.err b/python/file-test-data/extras/testTypes1.err similarity index 73% rename from python/test-data-2.0/extras/testTypes1.err rename to python/file-test-data/extras/testTypes1.err index d4590d30..ca748a76 100644 --- a/python/test-data-2.0/extras/testTypes1.err +++ b/python/file-test-data/extras/testTypes1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypes1.py", line 4, in + File "file-test-data/extras/testTypes1.py", line 4, in inc("1") WyppTypeError: "1" @@ -7,7 +7,7 @@ WyppTypeError: "1" Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/extras/testTypes1.py +## Datei file-test-data/extras/testTypes1.py ## Fehlerhafter Aufruf in Zeile 4: inc("1") diff --git a/python/test-data-2.0/extras/testTypes1.err-notypes b/python/file-test-data/extras/testTypes1.err-notypes similarity index 100% rename from python/test-data-2.0/extras/testTypes1.err-notypes rename to python/file-test-data/extras/testTypes1.err-notypes diff --git a/python/test-data-2.0/extras/testTypes1.out b/python/file-test-data/extras/testTypes1.out similarity index 100% rename from python/test-data-2.0/extras/testTypes1.out rename to python/file-test-data/extras/testTypes1.out diff --git a/python/test-data-2.0/extras/testTypes1.py b/python/file-test-data/extras/testTypes1.py similarity index 100% rename from python/test-data-2.0/extras/testTypes1.py rename to python/file-test-data/extras/testTypes1.py diff --git a/python/test-data-2.0/extras/testTypes2.err b/python/file-test-data/extras/testTypes2.err similarity index 73% rename from python/test-data-2.0/extras/testTypes2.err rename to python/file-test-data/extras/testTypes2.err index df170e1e..fa96aea6 100644 --- a/python/test-data-2.0/extras/testTypes2.err +++ b/python/file-test-data/extras/testTypes2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypes2.py", line 4, in + File "file-test-data/extras/testTypes2.py", line 4, in inc("1") WyppTypeError: "1" @@ -7,7 +7,7 @@ WyppTypeError: "1" Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/extras/testTypes2.py +## Datei file-test-data/extras/testTypes2.py ## Fehlerhafter Aufruf in Zeile 4: inc("1") diff --git a/python/test-data-2.0/extras/testTypes2.err-notypes b/python/file-test-data/extras/testTypes2.err-notypes similarity index 100% rename from python/test-data-2.0/extras/testTypes2.err-notypes rename to python/file-test-data/extras/testTypes2.err-notypes diff --git a/python/test-data-2.0/extras/testTypes2.out b/python/file-test-data/extras/testTypes2.out similarity index 100% rename from python/test-data-2.0/extras/testTypes2.out rename to python/file-test-data/extras/testTypes2.out diff --git a/python/test-data-2.0/extras/testTypes2.py b/python/file-test-data/extras/testTypes2.py similarity index 100% rename from python/test-data-2.0/extras/testTypes2.py rename to python/file-test-data/extras/testTypes2.py diff --git a/python/test-data-2.0/extras/testTypes2_ok.py b/python/file-test-data/extras/testTypes2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypes2_ok.py rename to python/file-test-data/extras/testTypes2_ok.py diff --git a/python/test-data-2.0/extras/testTypesDict1.err b/python/file-test-data/extras/testTypesDict1.err similarity index 100% rename from python/test-data-2.0/extras/testTypesDict1.err rename to python/file-test-data/extras/testTypesDict1.err diff --git a/python/test-data-2.0/extras/testTypesDict1.out b/python/file-test-data/extras/testTypesDict1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesDict1.out rename to python/file-test-data/extras/testTypesDict1.out diff --git a/python/test-data-2.0/extras/testTypesDict3.err b/python/file-test-data/extras/testTypesDict3.err similarity index 69% rename from python/test-data-2.0/extras/testTypesDict3.err rename to python/file-test-data/extras/testTypesDict3.err index d0058fe0..09717b3d 100644 --- a/python/test-data-2.0/extras/testTypesDict3.err +++ b/python/file-test-data/extras/testTypesDict3.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesDict3.py", line 11, in + File "file-test-data/extras/testTypesDict3.py", line 11, in foo({'y': func}) - File "test-data-2.0/extras/testTypesDict3.py", line 8, in foo + 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 test-data-2.0/extras/testTypesDict3.py +## Datei file-test-data/extras/testTypesDict3.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: diff --git a/python/test-data-2.0/extras/testTypesDict3.out b/python/file-test-data/extras/testTypesDict3.out similarity index 100% rename from python/test-data-2.0/extras/testTypesDict3.out rename to python/file-test-data/extras/testTypesDict3.out diff --git a/python/test-data-2.0/extras/testTypesDict3.py b/python/file-test-data/extras/testTypesDict3.py similarity index 100% rename from python/test-data-2.0/extras/testTypesDict3.py rename to python/file-test-data/extras/testTypesDict3.py diff --git a/python/test-data-2.0/extras/testTypesDict4.err b/python/file-test-data/extras/testTypesDict4.err similarity index 64% rename from python/test-data-2.0/extras/testTypesDict4.err rename to python/file-test-data/extras/testTypesDict4.err index 70aec66b..195ea6af 100644 --- a/python/test-data-2.0/extras/testTypesDict4.err +++ b/python/file-test-data/extras/testTypesDict4.err @@ -1,16 +1,16 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesDict4.py", line 14, in + File "file-test-data/extras/testTypesDict4.py", line 14, in bar({'y': func}) # error - File "test-data-2.0/extras/testTypesDict4.py", line 11, in bar + File "file-test-data/extras/testTypesDict4.py", line 11, in bar return foo(d) - File "test-data-2.0/extras/testTypesDict4.py", line 8, in foo + 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 test-data-2.0/extras/testTypesDict4.py +## Datei file-test-data/extras/testTypesDict4.py ## Rückgabetyp deklariert in Zeile 3: def foo(d: dict[str, Callable[[], str]]) -> list[str]: diff --git a/python/test-data-2.0/extras/testTypesDict4.out b/python/file-test-data/extras/testTypesDict4.out similarity index 100% rename from python/test-data-2.0/extras/testTypesDict4.out rename to python/file-test-data/extras/testTypesDict4.out diff --git a/python/test-data-2.0/extras/testTypesDict4.py b/python/file-test-data/extras/testTypesDict4.py similarity index 100% rename from python/test-data-2.0/extras/testTypesDict4.py rename to python/file-test-data/extras/testTypesDict4.py diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns.err b/python/file-test-data/extras/testTypesHigherOrderFuns.err similarity index 68% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns.err rename to python/file-test-data/extras/testTypesHigherOrderFuns.err index 6e781461..ab4217f9 100644 --- a/python/test-data-2.0/extras/testTypesHigherOrderFuns.err +++ b/python/file-test-data/extras/testTypesHigherOrderFuns.err @@ -1,14 +1,14 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesHigherOrderFuns.py", line 10, in + File "file-test-data/extras/testTypesHigherOrderFuns.py", line 10, in map(["hello", "1"], lambda x: x) - File "test-data-2.0/extras/testTypesHigherOrderFuns.py", line 7, in map + 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 test-data-2.0/extras/testTypesHigherOrderFuns.py +## Datei file-test-data/extras/testTypesHigherOrderFuns.py ## Rückgabetyp deklariert in Zeile 3: def map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]: diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns.out b/python/file-test-data/extras/testTypesHigherOrderFuns.out similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns.out rename to python/file-test-data/extras/testTypesHigherOrderFuns.out diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns.py b/python/file-test-data/extras/testTypesHigherOrderFuns.py similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns.py rename to python/file-test-data/extras/testTypesHigherOrderFuns.py diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.err rename to python/file-test-data/extras/testTypesHigherOrderFuns2_ok.err diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.out b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.out rename to python/file-test-data/extras/testTypesHigherOrderFuns2_ok.out diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.py b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns2_ok.py rename to python/file-test-data/extras/testTypesHigherOrderFuns2_ok.py diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns3.err b/python/file-test-data/extras/testTypesHigherOrderFuns3.err similarity index 78% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns3.err rename to python/file-test-data/extras/testTypesHigherOrderFuns3.err index 7c818faf..28bdb129 100644 --- a/python/test-data-2.0/extras/testTypesHigherOrderFuns3.err +++ b/python/file-test-data/extras/testTypesHigherOrderFuns3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesHigherOrderFuns3.py", line 38, in + File "file-test-data/extras/testTypesHigherOrderFuns3.py", line 38, in homePoints: Callable[[GameResult], int] = mkGamePoints(42) WyppTypeError: 42 @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/testTypesHigherOrderFuns3.py +## Datei file-test-data/extras/testTypesHigherOrderFuns3.py ## Fehlerhafter Aufruf in Zeile 38: homePoints: Callable[[GameResult], int] = mkGamePoints(42) diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns3.out b/python/file-test-data/extras/testTypesHigherOrderFuns3.out similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns3.out rename to python/file-test-data/extras/testTypesHigherOrderFuns3.out diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns3.py b/python/file-test-data/extras/testTypesHigherOrderFuns3.py similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns3.py rename to python/file-test-data/extras/testTypesHigherOrderFuns3.py diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.err rename to python/file-test-data/extras/testTypesHigherOrderFuns4_ok.err diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.out b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.out rename to python/file-test-data/extras/testTypesHigherOrderFuns4_ok.out diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.py b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns4_ok.py rename to python/file-test-data/extras/testTypesHigherOrderFuns4_ok.py diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.err rename to python/file-test-data/extras/testTypesHigherOrderFuns5_ok.err diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.out b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.out rename to python/file-test-data/extras/testTypesHigherOrderFuns5_ok.out diff --git a/python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.py b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypesHigherOrderFuns5_ok.py rename to python/file-test-data/extras/testTypesHigherOrderFuns5_ok.py diff --git a/python/test-data-2.0/extras/testTypesProtos1.err b/python/file-test-data/extras/testTypesProtos1.err similarity index 68% rename from python/test-data-2.0/extras/testTypesProtos1.err rename to python/file-test-data/extras/testTypesProtos1.err index 2b373448..1dbb65d6 100644 --- a/python/test-data-2.0/extras/testTypesProtos1.err +++ b/python/file-test-data/extras/testTypesProtos1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos1.py", line 21, in + File "file-test-data/extras/testTypesProtos1.py", line 21, in doSomething(Dog()) - File "test-data-2.0/extras/testTypesProtos1.py", line 19, in doSomething + File "file-test-data/extras/testTypesProtos1.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesProtos1.py +## Datei file-test-data/extras/testTypesProtos1.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/extras/testTypesProtos1.out b/python/file-test-data/extras/testTypesProtos1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos1.out rename to python/file-test-data/extras/testTypesProtos1.out diff --git a/python/test-data-2.0/extras/testTypesProtos1.py b/python/file-test-data/extras/testTypesProtos1.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos1.py rename to python/file-test-data/extras/testTypesProtos1.py diff --git a/python/test-data-2.0/extras/testTypesProtos2.err b/python/file-test-data/extras/testTypesProtos2.err similarity index 68% rename from python/test-data-2.0/extras/testTypesProtos2.err rename to python/file-test-data/extras/testTypesProtos2.err index ecf6e504..f25564ef 100644 --- a/python/test-data-2.0/extras/testTypesProtos2.err +++ b/python/file-test-data/extras/testTypesProtos2.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos2.py", line 21, in + File "file-test-data/extras/testTypesProtos2.py", line 21, in doSomething(Dog()) - File "test-data-2.0/extras/testTypesProtos2.py", line 19, in doSomething + File "file-test-data/extras/testTypesProtos2.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: 3.14 @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesProtos2.py +## Datei file-test-data/extras/testTypesProtos2.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/extras/testTypesProtos2.out b/python/file-test-data/extras/testTypesProtos2.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos2.out rename to python/file-test-data/extras/testTypesProtos2.out diff --git a/python/test-data-2.0/extras/testTypesProtos2.py b/python/file-test-data/extras/testTypesProtos2.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos2.py rename to python/file-test-data/extras/testTypesProtos2.py diff --git a/python/test-data-2.0/extras/testTypesProtos3.err b/python/file-test-data/extras/testTypesProtos3.err similarity index 69% rename from python/test-data-2.0/extras/testTypesProtos3.err rename to python/file-test-data/extras/testTypesProtos3.err index adde3200..e8d33de9 100644 --- a/python/test-data-2.0/extras/testTypesProtos3.err +++ b/python/file-test-data/extras/testTypesProtos3.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos3.py", line 21, in + File "file-test-data/extras/testTypesProtos3.py", line 21, in doSomething(Dog()) - File "test-data-2.0/extras/testTypesProtos3.py", line 19, in doSomething + File "file-test-data/extras/testTypesProtos3.py", line 19, in doSomething print(a.makeSound(3.14)) WyppTypeError: @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesProtos3.py +## Datei file-test-data/extras/testTypesProtos3.py ## Fehlerhafter Aufruf in Zeile 19: print(a.makeSound(3.14)) diff --git a/python/test-data-2.0/extras/testTypesProtos3.out b/python/file-test-data/extras/testTypesProtos3.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos3.out rename to python/file-test-data/extras/testTypesProtos3.out diff --git a/python/test-data-2.0/extras/testTypesProtos3.py b/python/file-test-data/extras/testTypesProtos3.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos3.py rename to python/file-test-data/extras/testTypesProtos3.py diff --git a/python/test-data-2.0/extras/testTypesProtos4.err b/python/file-test-data/extras/testTypesProtos4.err similarity index 65% rename from python/test-data-2.0/extras/testTypesProtos4.err rename to python/file-test-data/extras/testTypesProtos4.err index d5d98d93..a41011f4 100644 --- a/python/test-data-2.0/extras/testTypesProtos4.err +++ b/python/file-test-data/extras/testTypesProtos4.err @@ -1,9 +1,9 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos4.py", line 27, in + File "file-test-data/extras/testTypesProtos4.py", line 27, in print(foo(ConcreteWrong())) - File "test-data-2.0/extras/testTypesProtos4.py", line 24, in foo + File "file-test-data/extras/testTypesProtos4.py", line 24, in foo return fn(2) - File "test-data-2.0/extras/testTypesProtos4.py", line 20, in + 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 @@ -11,7 +11,7 @@ WyppTypeError: 2 Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/extras/testTypesProtos4.py +## Datei file-test-data/extras/testTypesProtos4.py ## Fehlerhafter Aufruf in Zeile 20: return lambda x: bar(x) # invalid call of bar with argument of type int diff --git a/python/test-data-2.0/extras/testTypesProtos4.out b/python/file-test-data/extras/testTypesProtos4.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos4.out rename to python/file-test-data/extras/testTypesProtos4.out diff --git a/python/test-data-2.0/extras/testTypesProtos4.py b/python/file-test-data/extras/testTypesProtos4.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos4.py rename to python/file-test-data/extras/testTypesProtos4.py diff --git a/python/test-data-2.0/extras/testTypesProtos5_ok.err b/python/file-test-data/extras/testTypesProtos5_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos5_ok.err rename to python/file-test-data/extras/testTypesProtos5_ok.err diff --git a/python/test-data-2.0/extras/testTypesProtos5_ok.out b/python/file-test-data/extras/testTypesProtos5_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos5_ok.out rename to python/file-test-data/extras/testTypesProtos5_ok.out diff --git a/python/test-data-2.0/extras/testTypesProtos5_ok.py b/python/file-test-data/extras/testTypesProtos5_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos5_ok.py rename to python/file-test-data/extras/testTypesProtos5_ok.py diff --git a/python/test-data-2.0/extras/testTypesProtos6.err b/python/file-test-data/extras/testTypesProtos6.err similarity index 56% rename from python/test-data-2.0/extras/testTypesProtos6.err rename to python/file-test-data/extras/testTypesProtos6.err index 5cccd08e..e20d49fd 100644 --- a/python/test-data-2.0/extras/testTypesProtos6.err +++ b/python/file-test-data/extras/testTypesProtos6.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos6.py", line 57, in + File "file-test-data/extras/testTypesProtos6.py", line 57, in print(computeTotalSize(root)) - File "test-data-2.0/extras/testTypesProtos6.py", line 50, in computeTotalSize + File "file-test-data/extras/testTypesProtos6.py", line 50, in computeTotalSize fs.accept(visitor) - File "test-data-2.0/extras/testTypesProtos6.py", line 19, in accept + File "file-test-data/extras/testTypesProtos6.py", line 19, in accept visitor.visitDirectory(self) - File "test-data-2.0/extras/testTypesProtos6.py", line 41, in visitDirectory + File "file-test-data/extras/testTypesProtos6.py", line 41, in visitDirectory c.accept(self) - File "test-data-2.0/extras/testTypesProtos6.py", line 28, in accept + File "file-test-data/extras/testTypesProtos6.py", line 28, in accept visitor.visitFile(self) WyppTypeError: <__wypp__.File object at 0x00> @@ -15,7 +15,7 @@ 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 test-data-2.0/extras/testTypesProtos6.py +## Datei file-test-data/extras/testTypesProtos6.py ## Fehlerhafter Aufruf in Zeile 28: visitor.visitFile(self) diff --git a/python/test-data-2.0/extras/testTypesProtos6.out b/python/file-test-data/extras/testTypesProtos6.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos6.out rename to python/file-test-data/extras/testTypesProtos6.out diff --git a/python/test-data-2.0/extras/testTypesProtos6.py b/python/file-test-data/extras/testTypesProtos6.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos6.py rename to python/file-test-data/extras/testTypesProtos6.py diff --git a/python/test-data-2.0/extras/testTypesProtos7.err b/python/file-test-data/extras/testTypesProtos7.err similarity index 55% rename from python/test-data-2.0/extras/testTypesProtos7.err rename to python/file-test-data/extras/testTypesProtos7.err index 1a10c6e2..d61b751e 100644 --- a/python/test-data-2.0/extras/testTypesProtos7.err +++ b/python/file-test-data/extras/testTypesProtos7.err @@ -1,13 +1,13 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos7.py", line 76, in + File "file-test-data/extras/testTypesProtos7.py", line 76, in print(computeTotalSize(root)) - File "test-data-2.0/extras/testTypesProtos7.py", line 69, in computeTotalSize + File "file-test-data/extras/testTypesProtos7.py", line 69, in computeTotalSize fs.accept(visitor) - File "test-data-2.0/extras/testTypesProtos7.py", line 36, in accept + File "file-test-data/extras/testTypesProtos7.py", line 36, in accept visitor.visitDirectory(self) - File "test-data-2.0/extras/testTypesProtos7.py", line 60, in visitDirectory + File "file-test-data/extras/testTypesProtos7.py", line 60, in visitDirectory c.accept(self) - File "test-data-2.0/extras/testTypesProtos7.py", line 47, in accept + File "file-test-data/extras/testTypesProtos7.py", line 47, in accept visitor.visitFile(self) WyppTypeError: File('notes.txt') @@ -15,7 +15,7 @@ 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 test-data-2.0/extras/testTypesProtos7.py +## Datei file-test-data/extras/testTypesProtos7.py ## Fehlerhafter Aufruf in Zeile 47: visitor.visitFile(self) diff --git a/python/test-data-2.0/extras/testTypesProtos7.out b/python/file-test-data/extras/testTypesProtos7.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos7.out rename to python/file-test-data/extras/testTypesProtos7.out diff --git a/python/test-data-2.0/extras/testTypesProtos7.py b/python/file-test-data/extras/testTypesProtos7.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos7.py rename to python/file-test-data/extras/testTypesProtos7.py diff --git a/python/test-data-2.0/extras/testTypesProtos8.err b/python/file-test-data/extras/testTypesProtos8.err similarity index 67% rename from python/test-data-2.0/extras/testTypesProtos8.err rename to python/file-test-data/extras/testTypesProtos8.err index e7b35965..a6ff1267 100644 --- a/python/test-data-2.0/extras/testTypesProtos8.err +++ b/python/file-test-data/extras/testTypesProtos8.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos8.py", line 12, in + File "file-test-data/extras/testTypesProtos8.py", line 12, in bar(Sub()) - File "test-data-2.0/extras/testTypesProtos8.py", line 10, in bar + File "file-test-data/extras/testTypesProtos8.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesProtos8.py +## Datei file-test-data/extras/testTypesProtos8.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") diff --git a/python/test-data-2.0/extras/testTypesProtos8.out b/python/file-test-data/extras/testTypesProtos8.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos8.out rename to python/file-test-data/extras/testTypesProtos8.out diff --git a/python/test-data-2.0/extras/testTypesProtos8.py b/python/file-test-data/extras/testTypesProtos8.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos8.py rename to python/file-test-data/extras/testTypesProtos8.py diff --git a/python/test-data-2.0/extras/testTypesProtos9.err b/python/file-test-data/extras/testTypesProtos9.err similarity index 67% rename from python/test-data-2.0/extras/testTypesProtos9.err rename to python/file-test-data/extras/testTypesProtos9.err index f2ff546e..5d271c3d 100644 --- a/python/test-data-2.0/extras/testTypesProtos9.err +++ b/python/file-test-data/extras/testTypesProtos9.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesProtos9.py", line 12, in + File "file-test-data/extras/testTypesProtos9.py", line 12, in bar(Sub()) - File "test-data-2.0/extras/testTypesProtos9.py", line 10, in bar + File "file-test-data/extras/testTypesProtos9.py", line 10, in bar b.foo(1, "foo") WyppTypeError: "foo" @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesProtos9.py +## Datei file-test-data/extras/testTypesProtos9.py ## Fehlerhafter Aufruf in Zeile 10: b.foo(1, "foo") diff --git a/python/test-data-2.0/extras/testTypesProtos9.out b/python/file-test-data/extras/testTypesProtos9.out similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos9.out rename to python/file-test-data/extras/testTypesProtos9.out diff --git a/python/test-data-2.0/extras/testTypesProtos9.py b/python/file-test-data/extras/testTypesProtos9.py similarity index 100% rename from python/test-data-2.0/extras/testTypesProtos9.py rename to python/file-test-data/extras/testTypesProtos9.py diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.err b/python/file-test-data/extras/testTypesRecordInheritance.err similarity index 100% rename from python/test-data-2.0/extras/testTypesRecordInheritance.err rename to python/file-test-data/extras/testTypesRecordInheritance.err diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.10 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testTypesRecordInheritance.err-3.10 rename to python/file-test-data/extras/testTypesRecordInheritance.err-3.10 diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.11 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testTypesRecordInheritance.err-3.11 rename to python/file-test-data/extras/testTypesRecordInheritance.err-3.11 diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.12 similarity index 70% rename from python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 rename to python/file-test-data/extras/testTypesRecordInheritance.err-3.12 index 554179b8..cb44a0b4 100644 --- a/python/test-data-2.0/extras/testTypesRecordInheritance.err-3.12 +++ b/python/file-test-data/extras/testTypesRecordInheritance.err-3.12 @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesRecordInheritance.py", line 18, in + File "file-test-data/extras/testTypesRecordInheritance.py", line 18, in Point3D(1,2, "foo") WyppTypeError: "foo" @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/testTypesRecordInheritance.py +## Datei file-test-data/extras/testTypesRecordInheritance.py ## Fehlerhafter Aufruf in Zeile 18: Point3D(1,2, "foo") diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.out b/python/file-test-data/extras/testTypesRecordInheritance.out similarity index 100% rename from python/test-data-2.0/extras/testTypesRecordInheritance.out rename to python/file-test-data/extras/testTypesRecordInheritance.out diff --git a/python/test-data-2.0/extras/testTypesRecordInheritance.py b/python/file-test-data/extras/testTypesRecordInheritance.py similarity index 100% rename from python/test-data-2.0/extras/testTypesRecordInheritance.py rename to python/file-test-data/extras/testTypesRecordInheritance.py diff --git a/python/test-data-2.0/extras/testTypesReturn.err b/python/file-test-data/extras/testTypesReturn.err similarity index 70% rename from python/test-data-2.0/extras/testTypesReturn.err rename to python/file-test-data/extras/testTypesReturn.err index 1f3d18ba..cea0d411 100644 --- a/python/test-data-2.0/extras/testTypesReturn.err +++ b/python/file-test-data/extras/testTypesReturn.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesReturn.py", line 8, in + File "file-test-data/extras/testTypesReturn.py", line 8, in foo(False) - File "test-data-2.0/extras/testTypesReturn.py", line 6, in foo + File "file-test-data/extras/testTypesReturn.py", line 6, in foo return 'you stupid' WyppTypeError: 'you stupid' @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesReturn.py +## Datei file-test-data/extras/testTypesReturn.py ## Rückgabetyp deklariert in Zeile 1: def foo(flag: bool) -> int: diff --git a/python/test-data-2.0/extras/testTypesReturn.out b/python/file-test-data/extras/testTypesReturn.out similarity index 100% rename from python/test-data-2.0/extras/testTypesReturn.out rename to python/file-test-data/extras/testTypesReturn.out diff --git a/python/test-data-2.0/extras/testTypesReturn.py b/python/file-test-data/extras/testTypesReturn.py similarity index 100% rename from python/test-data-2.0/extras/testTypesReturn.py rename to python/file-test-data/extras/testTypesReturn.py diff --git a/python/test-data-2.0/extras/testTypesSequence1.err b/python/file-test-data/extras/testTypesSequence1.err similarity index 72% rename from python/test-data-2.0/extras/testTypesSequence1.err rename to python/file-test-data/extras/testTypesSequence1.err index 215bdd12..cd7927a8 100644 --- a/python/test-data-2.0/extras/testTypesSequence1.err +++ b/python/file-test-data/extras/testTypesSequence1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesSequence1.py", line 10, in + File "file-test-data/extras/testTypesSequence1.py", line 10, in foo(1) # should fail WyppTypeError: 1 @@ -7,7 +7,7 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/extras/testTypesSequence1.py +## Datei file-test-data/extras/testTypesSequence1.py ## Fehlerhafter Aufruf in Zeile 10: foo(1) # should fail diff --git a/python/test-data-2.0/extras/testTypesSequence1.out b/python/file-test-data/extras/testTypesSequence1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesSequence1.out rename to python/file-test-data/extras/testTypesSequence1.out diff --git a/python/test-data-2.0/extras/testTypesSequence1.py b/python/file-test-data/extras/testTypesSequence1.py similarity index 100% rename from python/test-data-2.0/extras/testTypesSequence1.py rename to python/file-test-data/extras/testTypesSequence1.py diff --git a/python/test-data-2.0/extras/testTypesSequence2.err b/python/file-test-data/extras/testTypesSequence2.err similarity index 74% rename from python/test-data-2.0/extras/testTypesSequence2.err rename to python/file-test-data/extras/testTypesSequence2.err index 820c87f6..de4b451b 100644 --- a/python/test-data-2.0/extras/testTypesSequence2.err +++ b/python/file-test-data/extras/testTypesSequence2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesSequence2.py", line 11, in + File "file-test-data/extras/testTypesSequence2.py", line 11, in foo("Hello!") # should fail WyppTypeError: "Hello!" @@ -7,7 +7,7 @@ WyppTypeError: "Hello!" Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence[int]` als erstes Argument. Aber der übergebene Wert hat Typ `str`. -## Datei test-data-2.0/extras/testTypesSequence2.py +## Datei file-test-data/extras/testTypesSequence2.py ## Fehlerhafter Aufruf in Zeile 11: foo("Hello!") # should fail diff --git a/python/test-data-2.0/extras/testTypesSequence2.out b/python/file-test-data/extras/testTypesSequence2.out similarity index 100% rename from python/test-data-2.0/extras/testTypesSequence2.out rename to python/file-test-data/extras/testTypesSequence2.out diff --git a/python/test-data-2.0/extras/testTypesSequence2.py b/python/file-test-data/extras/testTypesSequence2.py similarity index 100% rename from python/test-data-2.0/extras/testTypesSequence2.py rename to python/file-test-data/extras/testTypesSequence2.py diff --git a/python/test-data-2.0/extras/testTypesSet1.err b/python/file-test-data/extras/testTypesSet1.err similarity index 100% rename from python/test-data-2.0/extras/testTypesSet1.err rename to python/file-test-data/extras/testTypesSet1.err diff --git a/python/test-data-2.0/extras/testTypesSet1.out b/python/file-test-data/extras/testTypesSet1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesSet1.out rename to python/file-test-data/extras/testTypesSet1.out diff --git a/python/test-data-2.0/extras/testTypesSubclassing1.err b/python/file-test-data/extras/testTypesSubclassing1.err similarity index 68% rename from python/test-data-2.0/extras/testTypesSubclassing1.err rename to python/file-test-data/extras/testTypesSubclassing1.err index 20112e32..ecfbeb53 100644 --- a/python/test-data-2.0/extras/testTypesSubclassing1.err +++ b/python/file-test-data/extras/testTypesSubclassing1.err @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesSubclassing1.py", line 29, in + File "file-test-data/extras/testTypesSubclassing1.py", line 29, in feedAnimal(dog) - File "test-data-2.0/extras/testTypesSubclassing1.py", line 26, in feedAnimal + File "file-test-data/extras/testTypesSubclassing1.py", line 26, in feedAnimal a.feed(AnimalFood('some cat food')) WyppTypeError: @@ -9,7 +9,7 @@ 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 test-data-2.0/extras/testTypesSubclassing1.py +## Datei file-test-data/extras/testTypesSubclassing1.py ## Fehlerhafter Aufruf in Zeile 26: a.feed(AnimalFood('some cat food')) diff --git a/python/test-data-2.0/extras/testTypesSubclassing1.out b/python/file-test-data/extras/testTypesSubclassing1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesSubclassing1.out rename to python/file-test-data/extras/testTypesSubclassing1.out diff --git a/python/test-data-2.0/extras/testTypesSubclassing1.py b/python/file-test-data/extras/testTypesSubclassing1.py similarity index 100% rename from python/test-data-2.0/extras/testTypesSubclassing1.py rename to python/file-test-data/extras/testTypesSubclassing1.py diff --git a/python/test-data-2.0/extras/testTypesTuple1.err b/python/file-test-data/extras/testTypesTuple1.err similarity index 72% rename from python/test-data-2.0/extras/testTypesTuple1.err rename to python/file-test-data/extras/testTypesTuple1.err index 75fab25a..71647939 100644 --- a/python/test-data-2.0/extras/testTypesTuple1.err +++ b/python/file-test-data/extras/testTypesTuple1.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testTypesTuple1.py", line 5, in + File "file-test-data/extras/testTypesTuple1.py", line 5, in foo(1) WyppTypeError: 1 @@ -7,7 +7,7 @@ WyppTypeError: 1 Der Aufruf der Funktion `foo` erwartet Wert vom Typ `tuple[int, ...]` als erstes Argument. Aber der übergebene Wert hat Typ `int`. -## Datei test-data-2.0/extras/testTypesTuple1.py +## Datei file-test-data/extras/testTypesTuple1.py ## Fehlerhafter Aufruf in Zeile 5: foo(1) diff --git a/python/test-data-2.0/extras/testTypesTuple1.out b/python/file-test-data/extras/testTypesTuple1.out similarity index 100% rename from python/test-data-2.0/extras/testTypesTuple1.out rename to python/file-test-data/extras/testTypesTuple1.out diff --git a/python/test-data-2.0/extras/testTypesTuple1.py b/python/file-test-data/extras/testTypesTuple1.py similarity index 100% rename from python/test-data-2.0/extras/testTypesTuple1.py rename to python/file-test-data/extras/testTypesTuple1.py diff --git a/python/test-data-2.0/extras/testTypesWrapperEq_ok.err b/python/file-test-data/extras/testTypesWrapperEq_ok.err similarity index 100% rename from python/test-data-2.0/extras/testTypesWrapperEq_ok.err rename to python/file-test-data/extras/testTypesWrapperEq_ok.err diff --git a/python/test-data-2.0/extras/testTypesWrapperEq_ok.out b/python/file-test-data/extras/testTypesWrapperEq_ok.out similarity index 100% rename from python/test-data-2.0/extras/testTypesWrapperEq_ok.out rename to python/file-test-data/extras/testTypesWrapperEq_ok.out diff --git a/python/test-data-2.0/extras/testTypesWrapperEq_ok.py b/python/file-test-data/extras/testTypesWrapperEq_ok.py similarity index 100% rename from python/test-data-2.0/extras/testTypesWrapperEq_ok.py rename to python/file-test-data/extras/testTypesWrapperEq_ok.py diff --git a/python/test-data-2.0/extras/testUnion2_ok.err b/python/file-test-data/extras/testUnion2_ok.err similarity index 100% rename from python/test-data-2.0/extras/testUnion2_ok.err rename to python/file-test-data/extras/testUnion2_ok.err diff --git a/python/test-data-2.0/extras/testUnion2_ok.out b/python/file-test-data/extras/testUnion2_ok.out similarity index 100% rename from python/test-data-2.0/extras/testUnion2_ok.out rename to python/file-test-data/extras/testUnion2_ok.out diff --git a/python/test-data-2.0/extras/testUnion2_ok.py b/python/file-test-data/extras/testUnion2_ok.py similarity index 100% rename from python/test-data-2.0/extras/testUnion2_ok.py rename to python/file-test-data/extras/testUnion2_ok.py diff --git a/python/test-data-2.0/extras/testUnion3_ok.err b/python/file-test-data/extras/testUnion3_ok.err similarity index 100% rename from python/test-data-2.0/extras/testUnion3_ok.err rename to python/file-test-data/extras/testUnion3_ok.err diff --git a/python/test-data-2.0/extras/testUnion3_ok.out b/python/file-test-data/extras/testUnion3_ok.out similarity index 100% rename from python/test-data-2.0/extras/testUnion3_ok.out rename to python/file-test-data/extras/testUnion3_ok.out diff --git a/python/test-data-2.0/extras/testUnion3_ok.py b/python/file-test-data/extras/testUnion3_ok.py similarity index 100% rename from python/test-data-2.0/extras/testUnion3_ok.py rename to python/file-test-data/extras/testUnion3_ok.py diff --git a/python/test-data-2.0/extras/testUnionLiteral_ok.err b/python/file-test-data/extras/testUnionLiteral_ok.err similarity index 100% rename from python/test-data-2.0/extras/testUnionLiteral_ok.err rename to python/file-test-data/extras/testUnionLiteral_ok.err diff --git a/python/test-data-2.0/extras/testUnionLiteral_ok.out b/python/file-test-data/extras/testUnionLiteral_ok.out similarity index 100% rename from python/test-data-2.0/extras/testUnionLiteral_ok.out rename to python/file-test-data/extras/testUnionLiteral_ok.out diff --git a/python/test-data-2.0/extras/testUnionLiteral_ok.py b/python/file-test-data/extras/testUnionLiteral_ok.py similarity index 100% rename from python/test-data-2.0/extras/testUnionLiteral_ok.py rename to python/file-test-data/extras/testUnionLiteral_ok.py diff --git a/python/test-data-2.0/extras/testUnionOfUnion_ok.err b/python/file-test-data/extras/testUnionOfUnion_ok.err similarity index 100% rename from python/test-data-2.0/extras/testUnionOfUnion_ok.err rename to python/file-test-data/extras/testUnionOfUnion_ok.err diff --git a/python/test-data-2.0/extras/testUnionOfUnion_ok.out b/python/file-test-data/extras/testUnionOfUnion_ok.out similarity index 100% rename from python/test-data-2.0/extras/testUnionOfUnion_ok.out rename to python/file-test-data/extras/testUnionOfUnion_ok.out diff --git a/python/test-data-2.0/extras/testUnionOfUnion_ok.py b/python/file-test-data/extras/testUnionOfUnion_ok.py similarity index 100% rename from python/test-data-2.0/extras/testUnionOfUnion_ok.py rename to python/file-test-data/extras/testUnionOfUnion_ok.py diff --git a/python/test-data-2.0/extras/testUnion_ok.err b/python/file-test-data/extras/testUnion_ok.err similarity index 100% rename from python/test-data-2.0/extras/testUnion_ok.err rename to python/file-test-data/extras/testUnion_ok.err diff --git a/python/test-data-2.0/extras/testUnion_ok.out b/python/file-test-data/extras/testUnion_ok.out similarity index 100% rename from python/test-data-2.0/extras/testUnion_ok.out rename to python/file-test-data/extras/testUnion_ok.out diff --git a/python/test-data-2.0/extras/testUnion_ok.py b/python/file-test-data/extras/testUnion_ok.py similarity index 100% rename from python/test-data-2.0/extras/testUnion_ok.py rename to python/file-test-data/extras/testUnion_ok.py diff --git a/python/test-data-2.0/extras/testWrongKeywordArg.err b/python/file-test-data/extras/testWrongKeywordArg.err similarity index 61% rename from python/test-data-2.0/extras/testWrongKeywordArg.err rename to python/file-test-data/extras/testWrongKeywordArg.err index 266a4908..77c95bd0 100644 --- a/python/test-data-2.0/extras/testWrongKeywordArg.err +++ b/python/file-test-data/extras/testWrongKeywordArg.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongKeywordArg.py", line 4, in + 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 test-data-2.0/extras/testWrongKeywordArg.py +## 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/test-data-2.0/extras/testWrongKeywordArg.out b/python/file-test-data/extras/testWrongKeywordArg.out similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg.out rename to python/file-test-data/extras/testWrongKeywordArg.out diff --git a/python/test-data-2.0/extras/testWrongKeywordArg.py b/python/file-test-data/extras/testWrongKeywordArg.py similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg.py rename to python/file-test-data/extras/testWrongKeywordArg.py diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.err b/python/file-test-data/extras/testWrongKeywordArg2.err similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg2.err rename to python/file-test-data/extras/testWrongKeywordArg2.err diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.10 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.10 similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg2.err-3.10 rename to python/file-test-data/extras/testWrongKeywordArg2.err-3.10 diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.11 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.11 similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg2.err-3.11 rename to python/file-test-data/extras/testWrongKeywordArg2.err-3.11 diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.12 similarity index 67% rename from python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 rename to python/file-test-data/extras/testWrongKeywordArg2.err-3.12 index d0c92f0f..c759db46 100644 --- a/python/test-data-2.0/extras/testWrongKeywordArg2.err-3.12 +++ b/python/file-test-data/extras/testWrongKeywordArg2.err-3.12 @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongKeywordArg2.py", line 8, in + 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 test-data-2.0/extras/testWrongKeywordArg2.py +## Datei file-test-data/extras/testWrongKeywordArg2.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(foo=3, y=4) \ No newline at end of file diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.out b/python/file-test-data/extras/testWrongKeywordArg2.out similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg2.out rename to python/file-test-data/extras/testWrongKeywordArg2.out diff --git a/python/test-data-2.0/extras/testWrongKeywordArg2.py b/python/file-test-data/extras/testWrongKeywordArg2.py similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg2.py rename to python/file-test-data/extras/testWrongKeywordArg2.py diff --git a/python/test-data-2.0/extras/testWrongKeywordArg3.err b/python/file-test-data/extras/testWrongKeywordArg3.err similarity index 67% rename from python/test-data-2.0/extras/testWrongKeywordArg3.err rename to python/file-test-data/extras/testWrongKeywordArg3.err index 38141cde..1053346e 100644 --- a/python/test-data-2.0/extras/testWrongKeywordArg3.err +++ b/python/file-test-data/extras/testWrongKeywordArg3.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongKeywordArg3.py", line 8, in + 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 test-data-2.0/extras/testWrongKeywordArg3.py +## Datei file-test-data/extras/testWrongKeywordArg3.py ## Fehlerhafter Aufruf in Zeile 8: p = Point(x=3, y=4, z=4) \ No newline at end of file diff --git a/python/test-data-2.0/extras/testWrongKeywordArg3.out b/python/file-test-data/extras/testWrongKeywordArg3.out similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg3.out rename to python/file-test-data/extras/testWrongKeywordArg3.out diff --git a/python/test-data-2.0/extras/testWrongKeywordArg3.py b/python/file-test-data/extras/testWrongKeywordArg3.py similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg3.py rename to python/file-test-data/extras/testWrongKeywordArg3.py diff --git a/python/test-data-2.0/extras/testWrongKeywordArg4.err b/python/file-test-data/extras/testWrongKeywordArg4.err similarity index 62% rename from python/test-data-2.0/extras/testWrongKeywordArg4.err rename to python/file-test-data/extras/testWrongKeywordArg4.err index 4d69ceeb..2163bbd2 100644 --- a/python/test-data-2.0/extras/testWrongKeywordArg4.err +++ b/python/file-test-data/extras/testWrongKeywordArg4.err @@ -1,12 +1,12 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongKeywordArg4.py", line 4, in + 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 test-data-2.0/extras/testWrongKeywordArg4.py +## 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/test-data-2.0/extras/testWrongKeywordArg4.out b/python/file-test-data/extras/testWrongKeywordArg4.out similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg4.out rename to python/file-test-data/extras/testWrongKeywordArg4.out diff --git a/python/test-data-2.0/extras/testWrongKeywordArg4.py b/python/file-test-data/extras/testWrongKeywordArg4.py similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg4.py rename to python/file-test-data/extras/testWrongKeywordArg4.py diff --git a/python/test-data-2.0/extras/testWrongKeywordArg5.err b/python/file-test-data/extras/testWrongKeywordArg5.err similarity index 58% rename from python/test-data-2.0/extras/testWrongKeywordArg5.err rename to python/file-test-data/extras/testWrongKeywordArg5.err index 16e274f9..0c2390bf 100644 --- a/python/test-data-2.0/extras/testWrongKeywordArg5.err +++ b/python/file-test-data/extras/testWrongKeywordArg5.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongKeywordArg5.py", line 4, in + File "file-test-data/extras/testWrongKeywordArg5.py", line 4, in foo() WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 1 Argument. Gegeben: keine Argumente -## Datei test-data-2.0/extras/testWrongKeywordArg5.py +## Datei file-test-data/extras/testWrongKeywordArg5.py ## Aufruf in Zeile 4: foo() \ No newline at end of file diff --git a/python/test-data-2.0/extras/testWrongKeywordArg5.out b/python/file-test-data/extras/testWrongKeywordArg5.out similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg5.out rename to python/file-test-data/extras/testWrongKeywordArg5.out diff --git a/python/test-data-2.0/extras/testWrongKeywordArg5.py b/python/file-test-data/extras/testWrongKeywordArg5.py similarity index 100% rename from python/test-data-2.0/extras/testWrongKeywordArg5.py rename to python/file-test-data/extras/testWrongKeywordArg5.py diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments.err b/python/file-test-data/extras/testWrongNumOfArguments.err similarity index 57% rename from python/test-data-2.0/extras/testWrongNumOfArguments.err rename to python/file-test-data/extras/testWrongNumOfArguments.err index 7a9173f4..6e4ffebd 100644 --- a/python/test-data-2.0/extras/testWrongNumOfArguments.err +++ b/python/file-test-data/extras/testWrongNumOfArguments.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongNumOfArguments.py", line 4, in + File "file-test-data/extras/testWrongNumOfArguments.py", line 4, in foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Funktion `foo` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/extras/testWrongNumOfArguments.py +## Datei file-test-data/extras/testWrongNumOfArguments.py ## Aufruf in Zeile 4: foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments.out b/python/file-test-data/extras/testWrongNumOfArguments.out similarity index 100% rename from python/test-data-2.0/extras/testWrongNumOfArguments.out rename to python/file-test-data/extras/testWrongNumOfArguments.out diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments.py b/python/file-test-data/extras/testWrongNumOfArguments.py similarity index 100% rename from python/test-data-2.0/extras/testWrongNumOfArguments.py rename to python/file-test-data/extras/testWrongNumOfArguments.py diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments2.err b/python/file-test-data/extras/testWrongNumOfArguments2.err similarity index 59% rename from python/test-data-2.0/extras/testWrongNumOfArguments2.err rename to python/file-test-data/extras/testWrongNumOfArguments2.err index 7f4971bc..3c767145 100644 --- a/python/test-data-2.0/extras/testWrongNumOfArguments2.err +++ b/python/file-test-data/extras/testWrongNumOfArguments2.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/testWrongNumOfArguments2.py", line 6, in + File "file-test-data/extras/testWrongNumOfArguments2.py", line 6, in c.foo(1) WyppTypeError: Anzahl der Argument passt nicht @@ -7,7 +7,7 @@ WyppTypeError: Anzahl der Argument passt nicht Methode `foo` der Klasse `C` benötigt 2 Argumente. Gegeben: 1 Argument -## Datei test-data-2.0/extras/testWrongNumOfArguments2.py +## Datei file-test-data/extras/testWrongNumOfArguments2.py ## Aufruf in Zeile 6: c.foo(1) \ No newline at end of file diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments2.out b/python/file-test-data/extras/testWrongNumOfArguments2.out similarity index 100% rename from python/test-data-2.0/extras/testWrongNumOfArguments2.out rename to python/file-test-data/extras/testWrongNumOfArguments2.out diff --git a/python/test-data-2.0/extras/testWrongNumOfArguments2.py b/python/file-test-data/extras/testWrongNumOfArguments2.py similarity index 100% rename from python/test-data-2.0/extras/testWrongNumOfArguments2.py rename to python/file-test-data/extras/testWrongNumOfArguments2.py diff --git a/python/test-data-2.0/extras/wrong-caused-by.err b/python/file-test-data/extras/wrong-caused-by.err similarity index 79% rename from python/test-data-2.0/extras/wrong-caused-by.err rename to python/file-test-data/extras/wrong-caused-by.err index 1ce1f63c..ca0e627c 100644 --- a/python/test-data-2.0/extras/wrong-caused-by.err +++ b/python/file-test-data/extras/wrong-caused-by.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "test-data-2.0/extras/wrong-caused-by.py", line 31, in + File "file-test-data/extras/wrong-caused-by.py", line 31, in mainStreetM.turnIntoStreet(redCarM) WyppTypeError: CarM(licensePlate='OG PY 123', color='rot') @@ -7,7 +7,7 @@ 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 test-data-2.0/extras/wrong-caused-by.py +## Datei file-test-data/extras/wrong-caused-by.py ## Fehlerhafter Aufruf in Zeile 31: mainStreetM.turnIntoStreet(redCarM) diff --git a/python/test-data-2.0/extras/wrong-caused-by.out b/python/file-test-data/extras/wrong-caused-by.out similarity index 100% rename from python/test-data-2.0/extras/wrong-caused-by.out rename to python/file-test-data/extras/wrong-caused-by.out diff --git a/python/test-data-2.0/extras/wrong-caused-by.py b/python/file-test-data/extras/wrong-caused-by.py similarity index 100% rename from python/test-data-2.0/extras/wrong-caused-by.py rename to python/file-test-data/extras/wrong-caused-by.py diff --git a/python/test-data-2.0/imports/fileWithBothImports.py b/python/file-test-data/imports/fileWithBothImports.py similarity index 100% rename from python/test-data-2.0/imports/fileWithBothImports.py rename to python/file-test-data/imports/fileWithBothImports.py diff --git a/python/test-data-2.0/imports/fileWithImport.py b/python/file-test-data/imports/fileWithImport.py similarity index 100% rename from python/test-data-2.0/imports/fileWithImport.py rename to python/file-test-data/imports/fileWithImport.py diff --git a/python/test-data-2.0/imports/fileWithoutImport.py b/python/file-test-data/imports/fileWithoutImport.py similarity index 100% rename from python/test-data-2.0/imports/fileWithoutImport.py rename to python/file-test-data/imports/fileWithoutImport.py diff --git a/python/test-data-2.0/imports/localMod.py b/python/file-test-data/imports/localMod.py similarity index 100% rename from python/test-data-2.0/imports/localMod.py rename to python/file-test-data/imports/localMod.py diff --git a/python/test-data-2.0/modules/B/mod.py b/python/file-test-data/modules/B/mod.py similarity index 100% rename from python/test-data-2.0/modules/B/mod.py rename to python/file-test-data/modules/B/mod.py diff --git a/python/fileTests-2.0.py b/python/fileTests-2.0.py deleted file mode 100644 index d1972bb1..00000000 --- a/python/fileTests-2.0.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path -from fileTestsLib import * - -directories = [Path("test-data-2.0/basics"), - Path("test-data-2.0/extras")] - -#directories = [Path("test-data-2.0/basics")] -#directories = [Path("test-data-2.0/extras")] - -checkInstall('test-data-2.0/imports/fileWithImport.py') -checkInstall('test-data-2.0/imports/fileWithoutImport.py') -checkInstall('test-data-2.0/imports/fileWithBothImports.py') - -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/fileTests.py b/python/fileTests.py index 5366f622..d23ae4e8 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -1,4 +1,21 @@ +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")] +checkInstall('file-test-data/imports/fileWithImport.py') +checkInstall('file-test-data/imports/fileWithoutImport.py') +checkInstall('file-test-data/imports/fileWithBothImports.py') + +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/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/test-data/testTypes2.py b/python/integration-test-data/testTypes2.py similarity index 100% rename from python/test-data/testTypes2.py rename to python/integration-test-data/testTypes2.py 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_ok.py b/python/integration-test-data/testTypesSet1_ok.py similarity index 100% rename from python/test-data/testTypesSet1_ok.py rename to python/integration-test-data/testTypesSet1_ok.py diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index 884c34ee..a6ef1508 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -43,52 +43,52 @@ def check(self, file, testFile, ecode, tycheck=True): 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] + 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) + 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}/src/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 succeeded. Great!', res.stdout) diff --git a/python/test-data-2.0/basics/stack.err b/python/test-data-2.0/basics/stack.err deleted file mode 100644 index 115c2f97..00000000 --- a/python/test-data-2.0/basics/stack.err +++ /dev/null @@ -1,13 +0,0 @@ -Traceback (most recent call last): - File "test-data-2.0/basics/stack.py", line 7, in - factorial(5) - File "test-data-2.0/basics/stack.py", line 5, in factorial - return factorial(n - 1) * n - File "test-data-2.0/basics/stack.py", line 5, in factorial - return factorial(n - 1) * n - File "test-data-2.0/basics/stack.py", line 5, in factorial - return factorial(n - 1) * n - [Previous line repeated 2 more times] - File "test-data-2.0/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-2.0/extras/main.py b/python/test-data-2.0/extras/main.py deleted file mode 100644 index d6036d5d..00000000 --- a/python/test-data-2.0/extras/main.py +++ /dev/null @@ -1,4 +0,0 @@ -# WYPP_TEST_CONFIG: {"args": ["--extra-dir", "test-data-2.0/modules/B"], "pythonPath": "test-data-2.0/modules/B"} -import mod - -print(mod.foo(1)) diff --git a/python/test-data-2.0/extras/testArgs_ok.out b/python/test-data-2.0/extras/testArgs_ok.out deleted file mode 100644 index 0d379756..00000000 --- a/python/test-data-2.0/extras/testArgs_ok.out +++ /dev/null @@ -1 +0,0 @@ -['test-data-2.0/extras/testArgs_ok.py', 'ARG_1', 'ARG_2'] diff --git a/python/test-data-2.0/extras/testCheck_ok.out b/python/test-data-2.0/extras/testCheck_ok.out deleted file mode 100644 index 19eb0013..00000000 --- a/python/test-data-2.0/extras/testCheck_ok.out +++ /dev/null @@ -1,5 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testCheck_ok.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-2.0/extras/testDeepEqBug_ok.out b/python/test-data-2.0/extras/testDeepEqBug_ok.out deleted file mode 100644 index c162a807..00000000 --- a/python/test-data-2.0/extras/testDeepEqBug_ok.out +++ /dev/null @@ -1,2 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testDeepEqBug_ok.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-2.0/extras/testFunEq_ok.out b/python/test-data-2.0/extras/testFunEq_ok.out deleted file mode 100644 index 2887acb4..00000000 --- a/python/test-data-2.0/extras/testFunEq_ok.out +++ /dev/null @@ -1,2 +0,0 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist -1 Test, 1 Fehler 🙁 diff --git a/python/test-data-2.0/extras/testIndexError.err b/python/test-data-2.0/extras/testIndexError.err deleted file mode 100644 index 0afd5aa3..00000000 --- a/python/test-data-2.0/extras/testIndexError.err +++ /dev/null @@ -1,6 +0,0 @@ -Traceback (most recent call last): - File "test-data-2.0/extras/testIndexError.py", line 6, in - foo([1,2,3]) - File "test-data-2.0/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/test-data-2.0/extras/testTraceback.err b/python/test-data-2.0/extras/testTraceback.err deleted file mode 100644 index d9c2c210..00000000 --- a/python/test-data-2.0/extras/testTraceback.err +++ /dev/null @@ -1,6 +0,0 @@ -Traceback (most recent call last): - File "test-data-2.0/extras/testTraceback.py", line 9, in - foo(lst) - File "test-data-2.0/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-2.0/extras/testTraceback2.err b/python/test-data-2.0/extras/testTraceback2.err deleted file mode 100644 index 3102b5ed..00000000 --- a/python/test-data-2.0/extras/testTraceback2.err +++ /dev/null @@ -1,4 +0,0 @@ -File "/Users/swehr/devel/write-your-python-program/python/test-data-2.0/extras/testTraceback2.py", line 3 - lst = [1,2,3 - ^ -SyntaxError: '[' was never closed \ No newline at end of file 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/B/mod.py b/python/test-data/modules/B/mod.py deleted file mode 100644 index 1d7d2978..00000000 --- a/python/test-data/modules/B/mod.py +++ /dev/null @@ -1,5 +0,0 @@ -def bar(s: str) -> int: - return 0 - -def foo(i: int) -> int: - return bar(i) 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/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.out b/python/test-data/testCheck2.out deleted file mode 100644 index e69de29b..00000000 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/testDisappearingObject_01.err b/python/test-data/testDisappearingObject_01.err deleted file mode 100644 index e69de29b..00000000 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 68d291c2..00000000 --- a/python/test-data/testDisappearingObject_01.py +++ /dev/null @@ -1,75 +0,0 @@ -# This test comes from a bug reported by a student, 2025-05-8 -from __future__ import annotations - -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.err b/python/test-data/testDisappearingObject_02.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testDisappearingObject_02.out b/python/test-data/testDisappearingObject_02.out deleted file mode 100644 index e69de29b..00000000 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/testDisappearingObject_03.err b/python/test-data/testDisappearingObject_03.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testDisappearingObject_03.out b/python/test-data/testDisappearingObject_03.out deleted file mode 100644 index 3252461b..00000000 --- a/python/test-data/testDisappearingObject_03.out +++ /dev/null @@ -1,2 +0,0 @@ -['subdir'] -Directory('stefan') diff --git a/python/test-data/testDisappearingObject_03.py b/python/test-data/testDisappearingObject_03.py deleted file mode 100644 index 2a2057fd..00000000 --- a/python/test-data/testDisappearingObject_03.py +++ /dev/null @@ -1,76 +0,0 @@ -# This test comes from a bug reported by a student, 2025-05-8 -from __future__ import annotations -import abc - -class FileSystemEntry(abc.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: - 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/testHintParentheses4.py b/python/test-data/testHintParentheses4.py deleted file mode 100644 index 50cd6a52..00000000 --- a/python/test-data/testHintParentheses4.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations -from wypp import * -# See https://github.com/skogsbaer/write-your-python-program/issues/61 - -# Tests 'return' -def foo() -> dict(int, str): - pass - -foo() diff --git a/python/test-data/testHintParentheses5.py b/python/test-data/testHintParentheses5.py deleted file mode 100644 index 8e36e34f..00000000 --- a/python/test-data/testHintParentheses5.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations -from wypp import * -# See https://github.com/skogsbaer/write-your-python-program/issues/61 - -@record -class C: - x: list() - -C(1) diff --git a/python/test-data/testHintParentheses6.py b/python/test-data/testHintParentheses6.py deleted file mode 100644 index 17406ca9..00000000 --- a/python/test-data/testHintParentheses6.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations -from wypp import * -# See https://github.com/skogsbaer/write-your-python-program/issues/61 - -@record(mutable=True) -class C: - x: list(int) - -c = C([]) -c.x = 1 diff --git a/python/test-data/testInferReturnType.err b/python/test-data/testInferReturnType.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testInferReturnType.out b/python/test-data/testInferReturnType.out deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testInferReturnType.py b/python/test-data/testInferReturnType.py deleted file mode 100644 index f3358c0d..00000000 --- a/python/test-data/testInferReturnType.py +++ /dev/null @@ -1,4 +0,0 @@ -def foo(i: int): - return None - -foo(1) 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.out b/python/test-data/testInferReturnType2.out deleted file mode 100644 index e69de29b..00000000 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.err b/python/test-data/testInferReturnType3.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testInferReturnType3.out b/python/test-data/testInferReturnType3.out deleted file mode 100644 index e69de29b..00000000 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.out b/python/test-data/testInferReturnType4.out deleted file mode 100644 index e69de29b..00000000 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/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.out b/python/test-data/testNums.out deleted file mode 100644 index e69de29b..00000000 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/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.out b/python/test-data/testTypesCollections5.out deleted file mode 100644 index e69de29b..00000000 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_ok.py b/python/test-data/testTypesDict1_ok.py deleted file mode 100644 index 09866b3f..00000000 --- a/python/test-data/testTypesDict1_ok.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/testUnsortableDicts.err b/python/test-data/testUnsortableDicts.err deleted file mode 100644 index e69de29b..00000000 diff --git a/python/test-data/testUnsortableDicts.out b/python/test-data/testUnsortableDicts.out deleted file mode 100644 index 5b149202..00000000 --- a/python/test-data/testUnsortableDicts.out +++ /dev/null @@ -1 +0,0 @@ -1 erfolgreicher Test 😀 diff --git a/python/test-data/testUnsortableDicts.py b/python/test-data/testUnsortableDicts.py deleted file mode 100644 index ae06eb96..00000000 --- a/python/test-data/testUnsortableDicts.py +++ /dev/null @@ -1,8 +0,0 @@ -from wypp import * - -def foo(): - pass -def bar(): - pass - -check({bar: 2, foo: 1}, {foo: 1, bar: 2.00000000001}) diff --git a/python/test-data/testWrongNumOfArguments3.py b/python/test-data/testWrongNumOfArguments3.py deleted file mode 100644 index f7ea712f..00000000 --- a/python/test-data/testWrongNumOfArguments3.py +++ /dev/null @@ -1,6 +0,0 @@ -class C: - def foo(x: int, y: float) -> float: - return x + y - -c = C() -c.foo(1, 2, 3) diff --git a/python/test-data/typeEnums.py b/python/test-data/typeEnums.py deleted file mode 100644 index b1a305a2..00000000 --- a/python/test-data/typeEnums.py +++ /dev/null @@ -1,12 +0,0 @@ -from wypp import * - -Color = Literal['red', 'yellow', 'green'] - -def colorToNumber(c: Color) -> int: - if c == 'red': - return 0 - elif c == 'yellow': - return 1 - else: - return 2 - diff --git a/python/test-data/typeRecords.py b/python/test-data/typeRecords.py deleted file mode 100644 index a8ff1102..00000000 --- a/python/test-data/typeRecords.py +++ /dev/null @@ -1,17 +0,0 @@ -from wypp import * - -@record -class Person: - name: str - age: int - -def incAge(p: Person) -> Person: - return Person(p.name, p.age + 1) - -@record(mutable=True) -class MutablePerson: - name: str - age: int - -def mutableIncAge(p: MutablePerson) -> None: - p.age = p.age + 1 diff --git a/python/test-data/typeUnion.py b/python/test-data/typeUnion.py deleted file mode 100644 index a1c41364..00000000 --- a/python/test-data/typeUnion.py +++ /dev/null @@ -1,23 +0,0 @@ -from wypp import * - -@record -class Cat: - name: str - weight: int - -myCat = Cat('Pumpernickel', 2) - -@record -class Parrot: - name: str - sentence: str - -myParrot = Parrot('Mike', "Let's go to the punkrock show") - -Animal = Union[Cat, Parrot] - -def formatAnimal(a: Animal) -> str: - if type(a) == Cat: - return "Cat " + a.name - else: - return "Parrot " + a.name + " says: " + a.sentence From 97a2be2e5ae6ccf7e2f658196f25a457cfee6139 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 05:55:59 +0200 Subject: [PATCH 50/56] all tests expect install tests running for 3.12 and 3.13 --- python/TODO_nowrappers.md | 5 ++-- python/allTests | 4 +-- python/allTestsForPyVersion | 2 +- python/file-test-data/basics/async.err | 8 +++--- python/file-test-data/basics/async.err_en | 8 +++--- .../extras/declared-at-missing.err | 24 ++++++++++------- .../extras/declared-at-missing.err-3.10 | 12 --------- .../extras/declared-at-missing.err-3.11 | 12 --------- .../extras/declared-at-missing.err-3.12 | 16 ------------ .../extras/testABCMeta.err-3.10 | 4 --- .../extras/testABCMeta.err-3.11 | 4 --- .../file-test-data/extras/testForwardRef4.err | 12 ++++++--- .../extras/testForwardRef4.err-3.10 | 6 ----- .../extras/testForwardRef4.err-3.11 | 6 ----- .../extras/testForwardRef4.err-3.12 | 10 ------- .../file-test-data/extras/testForwardRef5.err | 26 +++++++++++-------- .../extras/testForwardRef5.err-3.10 | 12 --------- .../extras/testForwardRef5.err-3.11 | 12 --------- .../extras/testForwardRef5.err-3.12 | 16 ------------ .../extras/testRecordSetTypeForwardRef.err | 23 ++++++++++------ .../testRecordSetTypeForwardRef.err-3.10 | 12 --------- .../testRecordSetTypeForwardRef.err-3.11 | 12 --------- .../testRecordSetTypeForwardRef.err-3.12 | 19 -------------- .../extras/testRecordSetTypes.err | 23 ++++++++++------ .../extras/testRecordSetTypes.err-3.10 | 12 --------- .../extras/testRecordSetTypes.err-3.11 | 12 --------- .../extras/testRecordSetTypes.err-3.12 | 19 -------------- .../file-test-data/extras/testRecordTypes.err | 25 +++++++++++------- .../extras/testRecordTypes.err-3.12 | 17 ------------ .../file-test-data/extras/testTraceback2.err | 4 +-- .../extras/testTraceback2.err-3.10.0 | 4 --- .../extras/testTraceback2.err-3.9 | 4 --- .../extras/testTypesRecordInheritance.err | 25 +++++++++++------- .../testTypesRecordInheritance.err-3.10 | 12 --------- .../testTypesRecordInheritance.err-3.11 | 12 --------- .../testTypesRecordInheritance.err-3.12 | 17 ------------ .../extras/testWrongKeywordArg2.err | 15 ++++++----- .../extras/testWrongKeywordArg2.err-3.10 | 9 ------- .../extras/testWrongKeywordArg2.err-3.11 | 9 ------- .../extras/testWrongKeywordArg2.err-3.12 | 12 --------- python/fileTestsLib.py | 8 +++--- python/src/runner.py | 6 +++-- 42 files changed, 130 insertions(+), 380 deletions(-) delete mode 100644 python/file-test-data/extras/declared-at-missing.err-3.10 delete mode 100644 python/file-test-data/extras/declared-at-missing.err-3.11 delete mode 100644 python/file-test-data/extras/declared-at-missing.err-3.12 delete mode 100644 python/file-test-data/extras/testABCMeta.err-3.10 delete mode 100644 python/file-test-data/extras/testABCMeta.err-3.11 delete mode 100644 python/file-test-data/extras/testForwardRef4.err-3.10 delete mode 100644 python/file-test-data/extras/testForwardRef4.err-3.11 delete mode 100644 python/file-test-data/extras/testForwardRef4.err-3.12 delete mode 100644 python/file-test-data/extras/testForwardRef5.err-3.10 delete mode 100644 python/file-test-data/extras/testForwardRef5.err-3.11 delete mode 100644 python/file-test-data/extras/testForwardRef5.err-3.12 delete mode 100644 python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.10 delete mode 100644 python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.11 delete mode 100644 python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 delete mode 100644 python/file-test-data/extras/testRecordSetTypes.err-3.10 delete mode 100644 python/file-test-data/extras/testRecordSetTypes.err-3.11 delete mode 100644 python/file-test-data/extras/testRecordSetTypes.err-3.12 delete mode 100644 python/file-test-data/extras/testRecordTypes.err-3.12 delete mode 100644 python/file-test-data/extras/testTraceback2.err-3.10.0 delete mode 100644 python/file-test-data/extras/testTraceback2.err-3.9 delete mode 100644 python/file-test-data/extras/testTypesRecordInheritance.err-3.10 delete mode 100644 python/file-test-data/extras/testTypesRecordInheritance.err-3.11 delete mode 100644 python/file-test-data/extras/testTypesRecordInheritance.err-3.12 delete mode 100644 python/file-test-data/extras/testWrongKeywordArg2.err-3.10 delete mode 100644 python/file-test-data/extras/testWrongKeywordArg2.err-3.11 delete mode 100644 python/file-test-data/extras/testWrongKeywordArg2.err-3.12 diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 18583ad8..9028ba36 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,9 +1,8 @@ -* Integration tests -* Python 3.12, 3.13 and 3.14? +* Python 3.13 and 3.14? * Installation * Test windows +* README -* Typechecked console * Debug slow startup times * show "@record\nclass C" for record attributes diff --git a/python/allTests b/python/allTests index 70a3ed75..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.2 run +PYENV_VERSION=3.12 run +PYENV_VERSION=3.13 run diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion index 8e19b2fb..30667b8f 100755 --- a/python/allTestsForPyVersion +++ b/python/allTestsForPyVersion @@ -5,7 +5,7 @@ set -u cd $(dirname $0) -unit_test_path=src:tests:deps +unit_test_path=site-lib:src:tests:deps function prepare_integration_tests() { diff --git a/python/file-test-data/basics/async.err b/python/file-test-data/basics/async.err index c9c465a6..a17f2494 100644 --- a/python/file-test-data/basics/async.err +++ b/python/file-test-data/basics/async.err @@ -1,11 +1,11 @@ Traceback (most recent call last): File "file-test-data/basics/async.py", line 10, in asyncio.run(main()) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run + File "runners.py", line ?, in run return runner.run(main) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 129, in run + File "runners.py", line ?, in run signal.signal(signal.SIGINT, signal.default_int_handler) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete + 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") @@ -22,4 +22,4 @@ Aber der übergebene Wert hat Typ `str`. ## Typ deklariert in Zeile 3: -async def foo(i: int): \ No newline at end of file +async def foo(i: int): diff --git a/python/file-test-data/basics/async.err_en b/python/file-test-data/basics/async.err_en index 5ccb4ac0..e4a7d64e 100644 --- a/python/file-test-data/basics/async.err_en +++ b/python/file-test-data/basics/async.err_en @@ -1,11 +1,11 @@ Traceback (most recent call last): File "file-test-data/basics/async.py", line 10, in asyncio.run(main()) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 194, in run + File "runners.py", line ?, in run return runner.run(main) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/runners.py", line 129, in run + File "runners.py", line ?, in run signal.signal(signal.SIGINT, signal.default_int_handler) - File "/Users/swehr/.pyenv/versions/3.12.1/lib/python3.12/asyncio/base_events.py", line 684, in run_until_complete + 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") @@ -22,4 +22,4 @@ But the value given has type `str`. ## Type declared in line 3: -async def foo(i: int): \ No newline at end of file +async def foo(i: int): diff --git a/python/file-test-data/extras/declared-at-missing.err b/python/file-test-data/extras/declared-at-missing.err index ee8a9cb1..4393d1f0 100644 --- a/python/file-test-data/extras/declared-at-missing.err +++ b/python/file-test-data/extras/declared-at-missing.err @@ -1,12 +1,16 @@ Traceback (most recent call last): - File "declared-at-missing.py", line 22, in + File "file-test-data/extras/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, )) + +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 = Semester('AKI', '1. Semester 2020/21', (prog1, )) + +## Typ deklariert in Zeile 19: + + courses: tuple[CourseM, ...] \ No newline at end of file diff --git a/python/file-test-data/extras/declared-at-missing.err-3.10 b/python/file-test-data/extras/declared-at-missing.err-3.10 deleted file mode 100644 index 92b7295e..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/declared-at-missing.err-3.11 b/python/file-test-data/extras/declared-at-missing.err-3.11 deleted file mode 100644 index 92b7295e..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/declared-at-missing.err-3.12 b/python/file-test-data/extras/declared-at-missing.err-3.12 deleted file mode 100644 index 4393d1f0..00000000 --- a/python/file-test-data/extras/declared-at-missing.err-3.12 +++ /dev/null @@ -1,16 +0,0 @@ -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 = Semester('AKI', '1. Semester 2020/21', (prog1, )) - -## Typ deklariert in Zeile 19: - - courses: tuple[CourseM, ...] \ No newline at end of file diff --git a/python/file-test-data/extras/testABCMeta.err-3.10 b/python/file-test-data/extras/testABCMeta.err-3.10 deleted file mode 100644 index c6c172cb..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testABCMeta.err-3.11 b/python/file-test-data/extras/testABCMeta.err-3.11 deleted file mode 100644 index c6c172cb..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testForwardRef4.err b/python/file-test-data/extras/testForwardRef4.err index 3230ad3b..b644f3f6 100644 --- a/python/file-test-data/extras/testForwardRef4.err +++ b/python/file-test-data/extras/testForwardRef4.err @@ -1,6 +1,10 @@ Traceback (most recent call last): - File "testForwardRef4.py", line 11, in + File "file-test-data/extras/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 + +WyppTypeError: ungültiger Typ `FooX` + +## Datei file-test-data/extras/testForwardRef4.py +## Typ deklariert in Zeile 5: + + foo: 'FooX' diff --git a/python/file-test-data/extras/testForwardRef4.err-3.10 b/python/file-test-data/extras/testForwardRef4.err-3.10 deleted file mode 100644 index 41fc248c..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testForwardRef4.err-3.11 b/python/file-test-data/extras/testForwardRef4.err-3.11 deleted file mode 100644 index 41fc248c..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testForwardRef4.err-3.12 b/python/file-test-data/extras/testForwardRef4.err-3.12 deleted file mode 100644 index a2783f29..00000000 --- a/python/file-test-data/extras/testForwardRef4.err-3.12 +++ /dev/null @@ -1,10 +0,0 @@ -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: - - foo: 'FooX' \ No newline at end of file diff --git a/python/file-test-data/extras/testForwardRef5.err b/python/file-test-data/extras/testForwardRef5.err index 8e1e5127..c13aa2dd 100644 --- a/python/file-test-data/extras/testForwardRef5.err +++ b/python/file-test-data/extras/testForwardRef5.err @@ -1,12 +1,16 @@ Traceback (most recent call last): - File "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: + 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=[Car(color='red'), "Not A Car"]) + +## Typ deklariert in Zeile 11: + + cars: list['Car'] \ No newline at end of file diff --git a/python/file-test-data/extras/testForwardRef5.err-3.10 b/python/file-test-data/extras/testForwardRef5.err-3.10 deleted file mode 100644 index 45c30a41..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testForwardRef5.err-3.11 b/python/file-test-data/extras/testForwardRef5.err-3.11 deleted file mode 100644 index 45c30a41..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testForwardRef5.err-3.12 b/python/file-test-data/extras/testForwardRef5.err-3.12 deleted file mode 100644 index c13aa2dd..00000000 --- a/python/file-test-data/extras/testForwardRef5.err-3.12 +++ /dev/null @@ -1,16 +0,0 @@ -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=[Car(color='red'), "Not A Car"]) - -## Typ deklariert in Zeile 11: - - cars: list['Car'] \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordSetTypeForwardRef.err b/python/file-test-data/extras/testRecordSetTypeForwardRef.err index 2194265e..a4b6d328 100644 --- a/python/file-test-data/extras/testRecordSetTypeForwardRef.err +++ b/python/file-test-data/extras/testRecordSetTypeForwardRef.err @@ -1,12 +1,19 @@ Traceback (most recent call last): - File "testRecordSetTypeForwardRef.py", line 15, in + File "file-test-data/extras/testRecordSetTypeForwardRef.py", line 15, in m() - File "testRecordSetTypeForwardRef.py", line 13, in m + File "file-test-data/extras/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" +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: + + r.x = "hello" + +## Typ deklariert in Zeile 6: + + x: A \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.10 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.10 deleted file mode 100644 index 361ff706..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testRecordSetTypeForwardRef.err-3.11 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.11 deleted file mode 100644 index 361ff706..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 b/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 deleted file mode 100644 index a4b6d328..00000000 --- a/python/file-test-data/extras/testRecordSetTypeForwardRef.err-3.12 +++ /dev/null @@ -1,19 +0,0 @@ -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: - - r.x = "hello" - -## Typ deklariert in Zeile 6: - - x: A \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordSetTypes.err b/python/file-test-data/extras/testRecordSetTypes.err index 3186a26a..ce400994 100644 --- a/python/file-test-data/extras/testRecordSetTypes.err +++ b/python/file-test-data/extras/testRecordSetTypes.err @@ -1,12 +1,19 @@ Traceback (most recent call last): - File "testRecordSetTypes.py", line 12, in + File "file-test-data/extras/testRecordSetTypes.py", line 12, in m() - File "testRecordSetTypes.py", line 10, in m + File "file-test-data/extras/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" +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: + + r.x = "hello" + +## Typ deklariert in Zeile 5: + + x : int \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordSetTypes.err-3.10 b/python/file-test-data/extras/testRecordSetTypes.err-3.10 deleted file mode 100644 index 3a5b1593..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testRecordSetTypes.err-3.11 b/python/file-test-data/extras/testRecordSetTypes.err-3.11 deleted file mode 100644 index 3a5b1593..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testRecordSetTypes.err-3.12 b/python/file-test-data/extras/testRecordSetTypes.err-3.12 deleted file mode 100644 index ce400994..00000000 --- a/python/file-test-data/extras/testRecordSetTypes.err-3.12 +++ /dev/null @@ -1,19 +0,0 @@ -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: - - r.x = "hello" - -## Typ deklariert in Zeile 5: - - x : int \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordTypes.err b/python/file-test-data/extras/testRecordTypes.err index 766e93f1..aac97f56 100644 --- a/python/file-test-data/extras/testRecordTypes.err +++ b/python/file-test-data/extras/testRecordTypes.err @@ -1,12 +1,17 @@ Traceback (most recent call last): - File "testRecordTypes.py", line 8, in + File "file-test-data/extras/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') + +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 = Point(1, '5') + +## Typ deklariert in Zeile 6: + + y: int \ No newline at end of file diff --git a/python/file-test-data/extras/testRecordTypes.err-3.12 b/python/file-test-data/extras/testRecordTypes.err-3.12 deleted file mode 100644 index aac97f56..00000000 --- a/python/file-test-data/extras/testRecordTypes.err-3.12 +++ /dev/null @@ -1,17 +0,0 @@ -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 = Point(1, '5') - -## Typ deklariert in Zeile 6: - - y: int \ No newline at end of file diff --git a/python/file-test-data/extras/testTraceback2.err b/python/file-test-data/extras/testTraceback2.err index db8fd026..d6703684 100644 --- a/python/file-test-data/extras/testTraceback2.err +++ b/python/file-test-data/extras/testTraceback2.err @@ -1,4 +1,4 @@ -File "/Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testTraceback2.py", line 3 + File "testTraceback2.py", line ? lst = [1,2,3 ^ -SyntaxError: '[' was never closed \ No newline at end of file +SyntaxError: '[' was never closed diff --git a/python/file-test-data/extras/testTraceback2.err-3.10.0 b/python/file-test-data/extras/testTraceback2.err-3.10.0 deleted file mode 100644 index 2ac0a7ee..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testTraceback2.err-3.9 b/python/file-test-data/extras/testTraceback2.err-3.9 deleted file mode 100644 index 714d0a88..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testTypesRecordInheritance.err b/python/file-test-data/extras/testTypesRecordInheritance.err index 23bddaed..cb44a0b4 100644 --- a/python/file-test-data/extras/testTypesRecordInheritance.err +++ b/python/file-test-data/extras/testTypesRecordInheritance.err @@ -1,12 +1,17 @@ Traceback (most recent call last): - File "testTypesRecordInheritance.py", line 18, in + File "file-test-data/extras/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") + +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: + + z: int \ No newline at end of file diff --git a/python/file-test-data/extras/testTypesRecordInheritance.err-3.10 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.10 deleted file mode 100644 index b86d1393..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testTypesRecordInheritance.err-3.11 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.11 deleted file mode 100644 index b86d1393..00000000 --- a/python/file-test-data/extras/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/file-test-data/extras/testTypesRecordInheritance.err-3.12 b/python/file-test-data/extras/testTypesRecordInheritance.err-3.12 deleted file mode 100644 index cb44a0b4..00000000 --- a/python/file-test-data/extras/testTypesRecordInheritance.err-3.12 +++ /dev/null @@ -1,17 +0,0 @@ -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: - - z: int \ No newline at end of file diff --git a/python/file-test-data/extras/testWrongKeywordArg2.err b/python/file-test-data/extras/testWrongKeywordArg2.err index 07b5be20..c759db46 100644 --- a/python/file-test-data/extras/testWrongKeywordArg2.err +++ b/python/file-test-data/extras/testWrongKeywordArg2.err @@ -1,9 +1,12 @@ Traceback (most recent call last): - File "testWrongKeywordArg2.py", line 8, in + File "file-test-data/extras/testWrongKeywordArg2.py", line 8, in p = Point(foo=3, y=4) -WyppTypeError: missing a required argument: 'x' -context: record constructor Point(x: int, y: int) -> Self -declared at: test-data/testWrongKeywordArg2.py:4 -caused by: test-data/testWrongKeywordArg2.py:8 - | 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 = Point(foo=3, y=4) \ No newline at end of file diff --git a/python/file-test-data/extras/testWrongKeywordArg2.err-3.10 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.10 deleted file mode 100644 index 2daa463d..00000000 --- a/python/file-test-data/extras/testWrongKeywordArg2.err-3.10 +++ /dev/null @@ -1,9 +0,0 @@ -Traceback (most recent call last): - File "test-data/testWrongKeywordArg2.py", line 8, in - p = Point(foo=3, y=4) -WyppTypeError: missing a required argument: 'x' - -context: record constructor Point(x: int, y: int) -> Self -declared at: test-data/testWrongKeywordArg2.py:3 -caused by: test-data/testWrongKeywordArg2.py:8 - | p = Point(foo=3, y=4) diff --git a/python/file-test-data/extras/testWrongKeywordArg2.err-3.11 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.11 deleted file mode 100644 index 2daa463d..00000000 --- a/python/file-test-data/extras/testWrongKeywordArg2.err-3.11 +++ /dev/null @@ -1,9 +0,0 @@ -Traceback (most recent call last): - File "test-data/testWrongKeywordArg2.py", line 8, in - p = Point(foo=3, y=4) -WyppTypeError: missing a required argument: 'x' - -context: record constructor Point(x: int, y: int) -> Self -declared at: test-data/testWrongKeywordArg2.py:3 -caused by: test-data/testWrongKeywordArg2.py:8 - | p = Point(foo=3, y=4) diff --git a/python/file-test-data/extras/testWrongKeywordArg2.err-3.12 b/python/file-test-data/extras/testWrongKeywordArg2.err-3.12 deleted file mode 100644 index c759db46..00000000 --- a/python/file-test-data/extras/testWrongKeywordArg2.err-3.12 +++ /dev/null @@ -1,12 +0,0 @@ -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 = Point(foo=3, y=4) \ No newline at end of file diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 47778690..773ca5b5 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -202,6 +202,7 @@ def fixOutput(filePath: str): """ 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) @@ -296,8 +297,6 @@ def _check(testFile: str, checkOutputs: bool, ctx: TestContext, what: str) -> TestStatus: - if shouldSkip(testFile, ctx, minVersion): - return 'skipped' 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') @@ -374,6 +373,8 @@ def check(testFile: str, 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 = [] @@ -387,9 +388,10 @@ def check(testFile: str, 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=' (no typecheck)') + ctx=ctx, what=what) def checkBasic(testFile: str, ctx: TestContext = globalCtx): check(testFile, checkOutputs=False, ctx=ctx) diff --git a/python/src/runner.py b/python/src/runner.py index 0bf86d56..12111f2e 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -12,7 +12,8 @@ from runnerCommon import * FILES_TO_INSTALL = ['writeYourProgram.py', 'drawingLib.py', '__init__.py'] -TYPEGUARD_DIR = os.path.join(LIB_DIR, "..", "deps", "typeguard") +DEPS_DIR = os.path.join(LIB_DIR, "..", "deps") +TYPEGUARD_DIR = os.path.join(DEPS_DIR, "typeguard") TYPEGUARD_MODULE_NAME = 'typeguard' SITELIB_DIR = os.path.join(LIB_DIR, "..", "site-lib") @@ -143,7 +144,8 @@ def installLib(mode): targetDir = os.getenv('WYPP_INSTALL_DIR', site.USER_SITE) try: allEq1 = installFromDir(LIB_DIR, targetDir, INSTALLED_MODULE_NAME, FILES_TO_INSTALL) - allEq2 = installFromDir(TYPEGUARD_DIR, targetDir, TYPEGUARD_MODULE_NAME) + allEq2 = True # installFromDir(TYPEGUARD_DIR, targetDir, TYPEGUARD_MODULE_NAME) + #allEq3 = installFromDir(DEPS_DIR, targetDir, '.', 'typing_extensions.py') if allEq1 and allEq2: verbose(f'WYPP library in {targetDir} already up to date') if mode == InstallMode.installOnly: From 7e4cc056e2334d0eb94387e81dde2960434e079f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 10:02:11 +0200 Subject: [PATCH 51/56] finished big refactoring --- python/.ignore | 2 - python/.vscode/settings.json | 5 +- python/TODO_nowrappers.md | 1 + python/allTestsForPyVersion | 19 +- python/{deps => code}/typeguard/__init__.py | 0 python/{deps => code}/typeguard/_checkers.py | 0 python/{deps => code}/typeguard/_config.py | 0 .../{deps => code}/typeguard/_decorators.py | 0 .../{deps => code}/typeguard/_exceptions.py | 0 python/{deps => code}/typeguard/_functions.py | 0 .../{deps => code}/typeguard/_importhook.py | 0 python/{deps => code}/typeguard/_memo.py | 0 .../typeguard/_pytest_plugin.py | 0 .../{deps => code}/typeguard/_suppression.py | 0 .../{deps => code}/typeguard/_transformer.py | 0 .../typeguard/_union_transformer.py | 0 python/{deps => code}/typeguard/_utils.py | 0 python/{deps => code}/typeguard/py.typed | 0 python/{deps => code}/typing_extensions.py | 0 python/{src => code/wypp}/__init__.py | 13 +- python/{src => code/wypp}/ansi.py | 0 python/code/wypp/cmdlineArgs.py | 53 +++ python/code/wypp/constants.py | 6 + python/{src => code/wypp}/debug.py | 0 python/{src => code/wypp}/drawingLib.py | 0 python/{src => code/wypp}/errors.py | 0 python/code/wypp/exceptionHandler.py | 67 ++++ python/{src => code/wypp}/i18n.py | 0 python/{src => code/wypp}/instrument.py | 0 python/code/wypp/interactive.py | 63 +++ python/{src => code/wypp}/lang.py | 0 python/{src => code/wypp}/location.py | 0 python/{src => code/wypp}/myLogging.py | 0 python/{src => code/wypp}/myTypeguard.py | 0 python/{src => code/wypp}/parsecache.py | 0 python/{src => code/wypp}/paths.py | 0 python/{src => code/wypp}/records.py | 1 - python/{src => code/wypp}/renderTy.py | 0 python/{src => code/wypp}/replTester.py | 12 +- python/code/wypp/runCode.py | 111 ++++++ python/{src => code/wypp}/runYourProgram.py | 1 - python/code/wypp/runner.py | 105 +++++ python/{src => code/wypp}/stacktrace.py | 12 +- python/{src => code/wypp}/typecheck.py | 0 python/{src => code/wypp}/utils.py | 22 ++ python/code/wypp/version.py | 40 ++ python/{src => code/wypp}/writeYourProgram.py | 0 python/debug.py | 2 +- .../file-test-data/extras/invalidRecord.err | 11 - .../file-test-data/extras/testGetSource.err | 6 +- .../file-test-data/extras/testImpossible.err | 4 +- python/file-test-data/extras/testLiteral1.err | 6 +- python/file-test-data/extras/testTodo.err | 4 +- .../file-test-data/extras/testTypesDict1.err | 14 - .../file-test-data/extras/testTypesDict1.out | 0 .../file-test-data/extras/testTypesSet1.err | 14 - .../file-test-data/extras/testTypesSet1.out | 0 python/fileTests.py | 4 - python/fileTestsLib.py | 4 +- python/integration-tests/testIntegration.py | 8 +- python/pyrightconfig.json | 6 +- python/run | 2 +- python/run-repl-tester | 2 +- python/site-lib/README | 3 - python/site-lib/typeguard | 1 - python/site-lib/typing_extensions.py | 1 - python/site-lib/wypp | 1 - python/src/runner.py | 197 ---------- python/src/runner2.py | 369 ------------------ python/src/runnerCommon.py | 27 -- python/tests/test_utils.py | 2 +- pytrace-generator/main.py | 15 +- src/extension.ts | 55 ++- .../backend/backend.ts | 4 - .../backend/trace_generator.ts | 33 +- 75 files changed, 582 insertions(+), 746 deletions(-) delete mode 100644 python/.ignore rename python/{deps => code}/typeguard/__init__.py (100%) rename python/{deps => code}/typeguard/_checkers.py (100%) rename python/{deps => code}/typeguard/_config.py (100%) rename python/{deps => code}/typeguard/_decorators.py (100%) rename python/{deps => code}/typeguard/_exceptions.py (100%) rename python/{deps => code}/typeguard/_functions.py (100%) rename python/{deps => code}/typeguard/_importhook.py (100%) rename python/{deps => code}/typeguard/_memo.py (100%) rename python/{deps => code}/typeguard/_pytest_plugin.py (100%) rename python/{deps => code}/typeguard/_suppression.py (100%) rename python/{deps => code}/typeguard/_transformer.py (100%) rename python/{deps => code}/typeguard/_union_transformer.py (100%) rename python/{deps => code}/typeguard/_utils.py (100%) rename python/{deps => code}/typeguard/py.typed (100%) rename python/{deps => code}/typing_extensions.py (100%) rename python/{src => code/wypp}/__init__.py (84%) rename python/{src => code/wypp}/ansi.py (100%) create mode 100644 python/code/wypp/cmdlineArgs.py create mode 100644 python/code/wypp/constants.py rename python/{src => code/wypp}/debug.py (100%) rename python/{src => code/wypp}/drawingLib.py (100%) rename python/{src => code/wypp}/errors.py (100%) create mode 100644 python/code/wypp/exceptionHandler.py rename python/{src => code/wypp}/i18n.py (100%) rename python/{src => code/wypp}/instrument.py (100%) create mode 100644 python/code/wypp/interactive.py rename python/{src => code/wypp}/lang.py (100%) rename python/{src => code/wypp}/location.py (100%) rename python/{src => code/wypp}/myLogging.py (100%) rename python/{src => code/wypp}/myTypeguard.py (100%) rename python/{src => code/wypp}/parsecache.py (100%) rename python/{src => code/wypp}/paths.py (100%) rename python/{src => code/wypp}/records.py (99%) rename python/{src => code/wypp}/renderTy.py (100%) rename python/{src => code/wypp}/replTester.py (94%) create mode 100644 python/code/wypp/runCode.py rename python/{src => code/wypp}/runYourProgram.py (99%) create mode 100644 python/code/wypp/runner.py rename python/{src => code/wypp}/stacktrace.py (92%) rename python/{src => code/wypp}/typecheck.py (100%) rename python/{src => code/wypp}/utils.py (82%) create mode 100644 python/code/wypp/version.py rename python/{src => code/wypp}/writeYourProgram.py (100%) delete mode 100644 python/file-test-data/extras/testTypesDict1.err delete mode 100644 python/file-test-data/extras/testTypesDict1.out delete mode 100644 python/file-test-data/extras/testTypesSet1.err delete mode 100644 python/file-test-data/extras/testTypesSet1.out delete mode 100644 python/site-lib/README delete mode 120000 python/site-lib/typeguard delete mode 120000 python/site-lib/typing_extensions.py delete mode 120000 python/site-lib/wypp delete mode 100644 python/src/runner.py delete mode 100644 python/src/runner2.py delete mode 100644 python/src/runnerCommon.py 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 index 826290ef..1e488efe 100644 --- a/python/.vscode/settings.json +++ b/python/.vscode/settings.json @@ -1,7 +1,6 @@ { + "python.analysis.diagnosticMode": "workspace", "python.autoComplete.extraPaths": [ "/Users/swehr/.vscode/extensions/stefanwehr.write-your-python-program-1.3.2/python/src/" - ], - "python.analysis.typeCheckingMode": "basic", - "python.analysis.diagnosticMode": "workspace", + ] } diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 9028ba36..14fb86ca 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -2,6 +2,7 @@ * Installation * Test windows * README +* location matcher for vscode * Debug slow startup times * show "@record\nclass C" for record attributes diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion index 30667b8f..52d27349 100755 --- a/python/allTestsForPyVersion +++ b/python/allTestsForPyVersion @@ -5,16 +5,8 @@ set -u cd $(dirname $0) -unit_test_path=site-lib:src:tests:deps - -function prepare_integration_tests() -{ - echo "Preparing integration tests by installing 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() { @@ -37,13 +29,12 @@ function run_unit_tests() function run_integration_tests() { - prepare_integration_tests - echo "Running all integration tests, PYTHONPATH=$integ_test_path" + echo "Running all integration tests, PYTHONPATH=$integration_test_path" if [ -z "${1:-}" ]; then - PYTHONPATH=$integ_test_path python3 -m unittest integration-tests/test*.py + PYTHONPATH=$integration_test_path python3 -m unittest integration-tests/test*.py ecode=$? else - PYTHONPATH=$integ_test_path python3 -m unittest "$@" + PYTHONPATH=$integration_test_path python3 -m unittest "$@" ecode=$? fi echo "Done with integration tests" diff --git a/python/deps/typeguard/__init__.py b/python/code/typeguard/__init__.py similarity index 100% rename from python/deps/typeguard/__init__.py rename to python/code/typeguard/__init__.py diff --git a/python/deps/typeguard/_checkers.py b/python/code/typeguard/_checkers.py similarity index 100% rename from python/deps/typeguard/_checkers.py rename to python/code/typeguard/_checkers.py diff --git a/python/deps/typeguard/_config.py b/python/code/typeguard/_config.py similarity index 100% rename from python/deps/typeguard/_config.py rename to python/code/typeguard/_config.py diff --git a/python/deps/typeguard/_decorators.py b/python/code/typeguard/_decorators.py similarity index 100% rename from python/deps/typeguard/_decorators.py rename to python/code/typeguard/_decorators.py diff --git a/python/deps/typeguard/_exceptions.py b/python/code/typeguard/_exceptions.py similarity index 100% rename from python/deps/typeguard/_exceptions.py rename to python/code/typeguard/_exceptions.py diff --git a/python/deps/typeguard/_functions.py b/python/code/typeguard/_functions.py similarity index 100% rename from python/deps/typeguard/_functions.py rename to python/code/typeguard/_functions.py diff --git a/python/deps/typeguard/_importhook.py b/python/code/typeguard/_importhook.py similarity index 100% rename from python/deps/typeguard/_importhook.py rename to python/code/typeguard/_importhook.py diff --git a/python/deps/typeguard/_memo.py b/python/code/typeguard/_memo.py similarity index 100% rename from python/deps/typeguard/_memo.py rename to python/code/typeguard/_memo.py diff --git a/python/deps/typeguard/_pytest_plugin.py b/python/code/typeguard/_pytest_plugin.py similarity index 100% rename from python/deps/typeguard/_pytest_plugin.py rename to python/code/typeguard/_pytest_plugin.py diff --git a/python/deps/typeguard/_suppression.py b/python/code/typeguard/_suppression.py similarity index 100% rename from python/deps/typeguard/_suppression.py rename to python/code/typeguard/_suppression.py diff --git a/python/deps/typeguard/_transformer.py b/python/code/typeguard/_transformer.py similarity index 100% rename from python/deps/typeguard/_transformer.py rename to python/code/typeguard/_transformer.py diff --git a/python/deps/typeguard/_union_transformer.py b/python/code/typeguard/_union_transformer.py similarity index 100% rename from python/deps/typeguard/_union_transformer.py rename to python/code/typeguard/_union_transformer.py diff --git a/python/deps/typeguard/_utils.py b/python/code/typeguard/_utils.py similarity index 100% rename from python/deps/typeguard/_utils.py rename to python/code/typeguard/_utils.py diff --git a/python/deps/typeguard/py.typed b/python/code/typeguard/py.typed similarity index 100% rename from python/deps/typeguard/py.typed rename to python/code/typeguard/py.typed diff --git a/python/deps/typing_extensions.py b/python/code/typing_extensions.py similarity index 100% rename from python/deps/typing_extensions.py rename to python/code/typing_extensions.py diff --git a/python/src/__init__.py b/python/code/wypp/__init__.py similarity index 84% rename from python/src/__init__.py rename to python/code/wypp/__init__.py index 47c68aa8..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,7 +31,11 @@ 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 diff --git a/python/src/ansi.py b/python/code/wypp/ansi.py similarity index 100% rename from python/src/ansi.py rename to python/code/wypp/ansi.py 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/src/errors.py b/python/code/wypp/errors.py similarity index 100% rename from python/src/errors.py rename to python/code/wypp/errors.py 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/src/i18n.py b/python/code/wypp/i18n.py similarity index 100% rename from python/src/i18n.py rename to python/code/wypp/i18n.py diff --git a/python/src/instrument.py b/python/code/wypp/instrument.py similarity index 100% rename from python/src/instrument.py rename to python/code/wypp/instrument.py 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/src/lang.py b/python/code/wypp/lang.py similarity index 100% rename from python/src/lang.py rename to python/code/wypp/lang.py diff --git a/python/src/location.py b/python/code/wypp/location.py similarity index 100% rename from python/src/location.py rename to python/code/wypp/location.py diff --git a/python/src/myLogging.py b/python/code/wypp/myLogging.py similarity index 100% rename from python/src/myLogging.py rename to python/code/wypp/myLogging.py diff --git a/python/src/myTypeguard.py b/python/code/wypp/myTypeguard.py similarity index 100% rename from python/src/myTypeguard.py rename to python/code/wypp/myTypeguard.py diff --git a/python/src/parsecache.py b/python/code/wypp/parsecache.py similarity index 100% rename from python/src/parsecache.py rename to python/code/wypp/parsecache.py diff --git a/python/src/paths.py b/python/code/wypp/paths.py similarity index 100% rename from python/src/paths.py rename to python/code/wypp/paths.py diff --git a/python/src/records.py b/python/code/wypp/records.py similarity index 99% rename from python/src/records.py rename to python/code/wypp/records.py index 4598ac35..21eb3122 100644 --- a/python/src/records.py +++ b/python/code/wypp/records.py @@ -81,7 +81,6 @@ def _setattr(obj, name, v): setattr(cls, "__setattr__", lambda obj, k, v: _call_with_frames_removed(_setattr, obj, k, v)) return cls -@typing.dataclass_transform() def record(cls=None, mutable=False, globals={}, locals={}): ns = myTypeguard.Namespaces(globals, locals) def wrap(cls: type): diff --git a/python/src/renderTy.py b/python/code/wypp/renderTy.py similarity index 100% rename from python/src/renderTy.py rename to python/code/wypp/renderTy.py diff --git a/python/src/replTester.py b/python/code/wypp/replTester.py similarity index 94% rename from python/src/replTester.py rename to python/code/wypp/replTester.py index 9368aa70..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 myLogging import * +import runCode usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m @@ -54,15 +58,9 @@ def parseCmdlineArgs(): enableVerbose() libDir = os.path.dirname(__file__) -siteDir = os.path.join(libDir, "..", "site-lib") -if siteDir not in sys.path: - sys.path.insert(0, siteDir) libFile = os.path.join(libDir, 'writeYourProgram.py') defs = globals() -from runner2 import runCode, importTypeguard -importTypeguard() - for lib in opts.libs: d = os.path.dirname(lib) if d not in sys.path: @@ -70,7 +68,7 @@ def parseCmdlineArgs(): for lib in opts.libs: verbose(f"Loading lib {lib}") - defs = runCode(lib, defs) + defs = runCode.runCode(lib, defs) totalFailures = 0 totalTests = 0 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 99% rename from python/src/runYourProgram.py rename to python/code/wypp/runYourProgram.py index 8f89a44d..672b88ba 100644 --- a/python/src/runYourProgram.py +++ b/python/code/wypp/runYourProgram.py @@ -2,7 +2,6 @@ # 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.'): 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/src/stacktrace.py b/python/code/wypp/stacktrace.py similarity index 92% rename from python/src/stacktrace.py rename to python/code/wypp/stacktrace.py index 630c06b4..4372010f 100644 --- a/python/src/stacktrace.py +++ b/python/code/wypp/stacktrace.py @@ -23,9 +23,14 @@ def isCallWithNextFrameRemoved(frame: types.FrameType): def isWyppFrame(frame: types.FrameType): modName = frame.f_globals.get("__name__") or '__wypp__' - return '__wypp_runYourProgram' in frame.f_globals or \ - modName == 'typeguard' or modName.startswith('typeguard.') or \ - modName == 'wypp' or modName.startswith('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 @@ -38,6 +43,7 @@ def isRunpyFrame(frame: types.FrameType) -> bool: def limitTraceback(frameList: list[types.FrameType], extraFrames: list[inspect.FrameInfo], filter: bool) -> traceback.StackSummary: + origFrameList = frameList if filter: # Step 1: remove all frames that appear after the first _call_with_frames_removed endIdx = len(frameList) diff --git a/python/src/typecheck.py b/python/code/wypp/typecheck.py similarity index 100% rename from python/src/typecheck.py rename to python/code/wypp/typecheck.py diff --git a/python/src/utils.py b/python/code/wypp/utils.py similarity index 82% rename from python/src/utils.py rename to python/code/wypp/utils.py index 41a9d05a..736cbab9 100644 --- a/python/src/utils.py +++ b/python/code/wypp/utils.py @@ -1,5 +1,6 @@ from typing import * import os +import sys from contextlib import contextmanager P = ParamSpec("P") @@ -78,3 +79,24 @@ def underTest(value: bool = True): 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 100% rename from python/src/writeYourProgram.py rename to python/code/wypp/writeYourProgram.py diff --git a/python/debug.py b/python/debug.py index f3edd5e2..e2660da4 100644 --- a/python/debug.py +++ b/python/debug.py @@ -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 # type: ignore runner.main(globals(), args) diff --git a/python/file-test-data/extras/invalidRecord.err b/python/file-test-data/extras/invalidRecord.err index 25ad0647..4665becc 100644 --- a/python/file-test-data/extras/invalidRecord.err +++ b/python/file-test-data/extras/invalidRecord.err @@ -1,14 +1,3 @@ -Traceback (most recent call last): - File "src/instrument.py", line 116, in source_to_code - tree = transformModule(tree, pathStr) - File "src/instrument.py", line 88, in transformModule - newStmts = [transformStmt(stmt, outerClassName=None, path=path) for stmt in body] - File "src/instrument.py", line 71, in transformStmt - newDecoratorList = [transformDecorator(e, path=path) for e in decoratorList] - File "src/instrument.py", line 51, in transformDecorator - raise errors.WyppTypeError.invalidRecordAnnotation(loc) - File "src/errors.py", line 103, in invalidRecordAnnotation - raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültige Record-Definition diff --git a/python/file-test-data/extras/testGetSource.err b/python/file-test-data/extras/testGetSource.err index 87d36f03..3605d652 100644 --- a/python/file-test-data/extras/testGetSource.err +++ b/python/file-test-data/extras/testGetSource.err @@ -1,9 +1,9 @@ Traceback (most recent call last): File "file-test-data/extras/testGetSource.py", line 11, in Art = Literal('klein','mittag') # <= problem is here - File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall + File "code/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) - File "src/errors.py", line 79, in invalidType + File "code/wypp/errors.py", line 79, in invalidType raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` @@ -13,4 +13,4 @@ Wolltest du `Literal['klein', 'mittag']` schreiben? ## Datei file-test-data/extras/testGetSource.py ## Typ deklariert in Zeile 11: -Art = Literal('klein','mittag') # <= problem is here \ No newline at end of file +Art = Literal('klein','mittag') # <= problem is here diff --git a/python/file-test-data/extras/testImpossible.err b/python/file-test-data/extras/testImpossible.err index a85724f2..de4ec37a 100644 --- a/python/file-test-data/extras/testImpossible.err +++ b/python/file-test-data/extras/testImpossible.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "file-test-data/extras/testImpossible.py", line 3, in impossible() - File "site-lib/wypp/writeYourProgram.py", line 331, in impossible + File "code/wypp/writeYourProgram.py", line 331, in impossible raise errors.ImpossibleError(msg) -Das Unmögliche ist passiert! \ No newline at end of file +Das Unmögliche ist passiert! diff --git a/python/file-test-data/extras/testLiteral1.err b/python/file-test-data/extras/testLiteral1.err index 82e16bd8..015899fb 100644 --- a/python/file-test-data/extras/testLiteral1.err +++ b/python/file-test-data/extras/testLiteral1.err @@ -1,9 +1,9 @@ Traceback (most recent call last): File "file-test-data/extras/testLiteral1.py", line 3, in T = Literal('a', 'b') - File "site-lib/wypp/writeYourProgram.py", line 92, in _invalidCall + File "code/wypp/writeYourProgram.py", line 92, in _invalidCall raise errors.WyppTypeError.invalidType(tyStr, loc) - File "src/errors.py", line 79, in invalidType + File "code/wypp/errors.py", line 79, in invalidType raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('a', 'b')` @@ -13,4 +13,4 @@ Wolltest du `Literal['a', 'b']` schreiben? ## Datei file-test-data/extras/testLiteral1.py ## Typ deklariert in Zeile 3: -T = Literal('a', 'b') \ No newline at end of file +T = Literal('a', 'b') diff --git a/python/file-test-data/extras/testTodo.err b/python/file-test-data/extras/testTodo.err index 8d2a43de..ae916125 100644 --- a/python/file-test-data/extras/testTodo.err +++ b/python/file-test-data/extras/testTodo.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "file-test-data/extras/testTodo.py", line 3, in todo() - File "site-lib/wypp/writeYourProgram.py", line 326, in todo + File "code/wypp/writeYourProgram.py", line 326, in todo raise errors.TodoError(msg) -TODO \ No newline at end of file +TODO diff --git a/python/file-test-data/extras/testTypesDict1.err b/python/file-test-data/extras/testTypesDict1.err deleted file mode 100644 index 34181724..00000000 --- a/python/file-test-data/extras/testTypesDict1.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "testTypesDict1.py", line 7, in - appendSomething(d) - File "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/file-test-data/extras/testTypesDict1.out b/python/file-test-data/extras/testTypesDict1.out deleted file mode 100644 index e69de29b..00000000 diff --git a/python/file-test-data/extras/testTypesSet1.err b/python/file-test-data/extras/testTypesSet1.err deleted file mode 100644 index 549bc2ae..00000000 --- a/python/file-test-data/extras/testTypesSet1.err +++ /dev/null @@ -1,14 +0,0 @@ -Traceback (most recent call last): - File "testTypesSet1.py", line 7, in - appendSomething(l) - File "testTypesSet1.py", line 4, in appendSomething - l.add("foo") -WyppTypeError: got value of wrong type -given: 'foo' -expected: value of type int - -context: add(self, other: int) - ^^^ -declared at: site-lib/untypy/impl/interfaces/set.py:7 -caused by: test-data/testTypesSet1.py:4 - | l.add("foo") diff --git a/python/file-test-data/extras/testTypesSet1.out b/python/file-test-data/extras/testTypesSet1.out deleted file mode 100644 index e69de29b..00000000 diff --git a/python/fileTests.py b/python/fileTests.py index d23ae4e8..32f7b345 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -7,10 +7,6 @@ #directories = [Path("file-test-data/basics")] #directories = [Path("file-test-data/extras")] -checkInstall('file-test-data/imports/fileWithImport.py') -checkInstall('file-test-data/imports/fileWithoutImport.py') -checkInstall('file-test-data/imports/fileWithBothImports.py') - for d in directories: for file in d.iterdir(): if file.is_file(): diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py index 773ca5b5..a889445e 100644 --- a/python/fileTestsLib.py +++ b/python/fileTestsLib.py @@ -47,7 +47,7 @@ def parseArgs() -> TestOpts: scriptDir = os.path.dirname(__file__) return TestOpts( - cmd=f'{scriptDir}/src/runYourProgram.py', + cmd=f'{scriptDir}/code/wypp/runYourProgram.py', baseDir=scriptDir, startAt=args.start_at, only=args.only, @@ -232,7 +232,7 @@ def _runTest(testFile: str, cmd.append(lang) cmd.extend(args) env = os.environ.copy() - env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'site-lib')] + pythonPath) + 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: diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index a6ef1508..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): @@ -38,7 +38,7 @@ 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) @@ -89,6 +89,6 @@ class ReplTesterTests(unittest.TestCase): def test_replTester(self): d = shell.pwd() - cmd = f'python3 {d}/src/replTester.py {d}/integration-test-data/repl-test-lib.py --repl {d}/integration-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 succeeded. Great!', res.stdout) diff --git a/python/pyrightconfig.json b/python/pyrightconfig.json index a979e183..1aeadf45 100644 --- a/python/pyrightconfig.json +++ b/python/pyrightconfig.json @@ -2,9 +2,9 @@ // See https://microsoft.github.io/pyright/#/configuration?id=pyright-configuration "reportWildcardImportFromLibrary": false, "reportMissingTypeStubs": false, - "exclude": ["test-data-2.0", "test-data", "trash"], - "ignore": ["deps", "site-lib"], - "extraPaths": ["src"], + "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 b2aa91ab..91861067 100755 --- a/python/run +++ b/python/run @@ -8,6 +8,6 @@ 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/site-lib/README b/python/site-lib/README deleted file mode 100644 index c2e4f4a6..00000000 --- a/python/site-lib/README +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains symlinks for wypp, typeguard and typing_extensions. - -Add this directory to PYTHONPATH if you want to import these modules in-place. diff --git a/python/site-lib/typeguard b/python/site-lib/typeguard deleted file mode 120000 index d3949e20..00000000 --- a/python/site-lib/typeguard +++ /dev/null @@ -1 +0,0 @@ -../deps/typeguard/ \ No newline at end of file diff --git a/python/site-lib/typing_extensions.py b/python/site-lib/typing_extensions.py deleted file mode 120000 index 0e27af76..00000000 --- a/python/site-lib/typing_extensions.py +++ /dev/null @@ -1 +0,0 @@ -../deps/typing_extensions.py \ 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 12111f2e..00000000 --- a/python/src/runner.py +++ /dev/null @@ -1,197 +0,0 @@ -# Step 1 of the runner. Only installes typeguard and then passes control to runner2. The purpose -# of splitting the runner in two modules is to allow step2 to import typeguard. - -import sys -import argparse -import os -from pathlib import Path -import shutil -import site - -from myLogging import * -from runnerCommon import * - -FILES_TO_INSTALL = ['writeYourProgram.py', 'drawingLib.py', '__init__.py'] -DEPS_DIR = os.path.join(LIB_DIR, "..", "deps") -TYPEGUARD_DIR = os.path.join(DEPS_DIR, "typeguard") -TYPEGUARD_MODULE_NAME = 'typeguard' -SITELIB_DIR = os.path.join(LIB_DIR, "..", "site-lib") - -__wypp_runYourProgram = 1 - -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('--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: - 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) - -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 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 = True # installFromDir(TYPEGUARD_DIR, targetDir, TYPEGUARD_MODULE_NAME) - #allEq3 = installFromDir(DEPS_DIR, targetDir, '.', 'typing_extensions.py') - 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) - -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) - if args.verbose: - enableVerbose() - if args.debug: - enableDebug() - - verbose(f'VERBOSE={args.verbose}, DEBUG={args.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 or site.USER_SITE is None: - printStderr(f"User site-packages disabled ({site.USER_SITE}. This might cause problems importing wypp or typeguard.") - else: - verbose(f"Adding user site-package directory {site.USER_SITE} to sys.path") - sys.path.append(site.USER_SITE) - import runner2 - runner2.main(globals, args, restArgs) diff --git a/python/src/runner2.py b/python/src/runner2.py deleted file mode 100644 index 5feae112..00000000 --- a/python/src/runner2.py +++ /dev/null @@ -1,369 +0,0 @@ -# Step 2 of the runner. Assumes that typeguard has been installed so that this -# module can use it. - -import sys -import os -import json -import traceback -import argparse -import importlib -import re -import code -from modulefinder import ModuleFinder -import subprocess -import runpy -from dataclasses import dataclass - -# local imports -from runnerCommon import * -import stacktrace -import i18n -import paths -import instrument -from myLogging import * -import errors - -__wypp_runYourProgram = 1 - -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, 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)) - - -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(f'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 (f := mod.__file__): # type: ignore - realp = os.path.realpath(f) - good = False - for d in realdirs: - if realp.startswith(d): - good = True - break - if good: - res.append(name) - return res - -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 # FIXME: remove? - 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: - 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'] - 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() - -_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): - 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: - 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 typeguard at the top of the file because we might have to install it first. -def importTypeguard(): - global typeguard - try: - import typeguard # type: ignore - except ModuleNotFoundError as e: - printStderr(f"Module typeguard not found, sys.path={sys.path}: {e}") - die(1) - -@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 main(globals: dict, args: argparse.Namespace, restArgs: list[str]): - # assumes that runner.main has been run - - 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) - - importTypeguard() - - fileToRun: str = 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, doTypecheck=args.checkTypes) - - libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes) - - with (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 = runStudentCode(fileToRun, globals, args.checkRunnable, - doTypecheck=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, doTypecheck=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) - diff --git a/python/src/runnerCommon.py b/python/src/runnerCommon.py deleted file mode 100644 index 827137fa..00000000 --- a/python/src/runnerCommon.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import sys - -LIB_DIR = os.path.dirname(__file__) -INSTALLED_MODULE_NAME = 'wypp' - -__wypp_runYourProgram = 1 - -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/tests/test_utils.py b/python/tests/test_utils.py index 2f517b6c..47460980 100644 --- a/python/tests/test_utils.py +++ b/python/tests/test_utils.py @@ -1,5 +1,5 @@ import unittest -from src.utils import dropWhile +from utils import dropWhile class TestUtils(unittest.TestCase): diff --git a/pytrace-generator/main.py b/pytrace-generator/main.py index 3bff2807..1d8d545b 100644 --- a/pytrace-generator/main.py +++ b/pytrace-generator/main.py @@ -254,7 +254,7 @@ class TraceStep: stack: Stack heap: Heap stdout: str - traceback_text: str + traceback_text: str | None def format(self): step = { @@ -358,7 +358,8 @@ def trace_dispatch(self, frame, event, arg): skip = filename != self.skip_until elif not filename.startswith(os.path.dirname(self.filename)): skip = True - self.skip_until = frame.f_back.f_code.co_filename + if frame.f_back: + self.skip_until = frame.f_back.f_code.co_filename if skip: return self.trace_dispatch else: @@ -403,7 +404,8 @@ def trace_dispatch(self, frame, event, arg): traceback_text = traceback_text_tmp.getvalue() - step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), accumulated_stdout, traceback_text) + step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), + accumulated_stdout, traceback_text) is_annotation = next_source_line.startswith("@") should_display_step = not is_annotation @@ -468,6 +470,13 @@ def run_script(self, filename, script_str): # It's a bit hacky but probably the easiest solution script_str += "\npass\n" +# Add code directory of wypp to path +thisDir = os.path.normpath(os.path.dirname(__file__)) +codeDir = os.path.normpath(os.path.join(thisDir, '..', 'python', 'code')) +wyppDir = os.path.join(codeDir, 'wypp') +sys.path.insert(0, wyppDir) +sys.path.insert(0, codeDir) + # Add script directory to path sys.path.insert(0, os.path.dirname(filename)) diff --git a/src/extension.ts b/src/extension.ts index ade85008..81812b05 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -217,20 +217,43 @@ function disableTypechecking(context: vscode.ExtensionContext): boolean { return !!config[disableTypecheckingConfigKey]; } -function fixPythonConfig(context: vscode.ExtensionContext) { - const libDir = context.asAbsolutePath('python/src/'); - const pyComplConfig = vscode.workspace.getConfiguration("python.autoComplete"); - const oldPath: string[] = pyComplConfig.get("extraPaths") || []; - const newPath = oldPath.filter(v => { - return !v.includes(extensionId); - }); - if (!newPath.includes(libDir)) { - newPath.push(libDir); +async function fixPylanceConfig( + context: vscode.ExtensionContext, + folder?: vscode.WorkspaceFolder +) { + // disable warnings about wildcard imports, add wypp to pylance's extraPaths + const libDir = context.asAbsolutePath('python/code/'); + + // Use the "python" section; Pylance contributes python.analysis.* + const cfg = vscode.workspace.getConfiguration('python', folder?.uri); + const target = folder ? vscode.ConfigurationTarget.WorkspaceFolder + : vscode.ConfigurationTarget.Workspace; + + // Read existing overrides (don’t clobber other rules) + const keyOverride = 'analysis.diagnosticSeverityOverrides'; + const overrides = cfg.get>(keyOverride) ?? {}; + if (overrides.reportWildcardImportFromLibrary !== 'none') { + const updated = { + ...overrides, + reportWildcardImportFromLibrary: 'none', + }; + await cfg.update( + 'analysis.diagnosticSeverityOverrides', + updated, + target + ); } - pyComplConfig.update("extraPaths", newPath); - // const pyLintConfig = vscode.workspace.getConfiguration("python.linting"); - // pyLintConfig.update("enabled", false); + const keyExtraPaths = 'analysis.extraPaths'; + const extra = cfg.get(keyExtraPaths) ?? []; + if (!extra.includes(libDir)) { + const updated = [...extra, libDir]; + await cfg.update( + keyExtraPaths, + [...extra, libDir], + target + ); + } } class Location implements vscode.TerminalLink { @@ -329,7 +352,7 @@ export class PythonExtension { // this method is called when your extension is activated // your extension is activated the very first time the command is executed -export function activate(context: vscode.ExtensionContext) { +export async function activate(context: vscode.ExtensionContext) { disposables.forEach(d => d.dispose()); console.log('Activating extension ' + extensionId); @@ -337,16 +360,15 @@ export function activate(context: vscode.ExtensionContext) { const outChannel = vscode.window.createOutputChannel("Write Your Python Program"); disposables.push(outChannel); - fixPythonConfig(context); + await fixPylanceConfig(context); const terminals: { [name: string]: TerminalContext } = {}; installButton("Write Your Python Program", undefined); const linkProvider = new TerminalLinkProvider(terminals); const pyExt = new PythonExtension(); + const runProg = context.asAbsolutePath('python/code/wypp/runYourProgram.py'); - // Run - const runProg = context.asAbsolutePath('python/src/runYourProgram.py'); installCmd( context, "run", @@ -383,7 +405,6 @@ export function activate(context: vscode.ExtensionContext) { "WYPP - RUN", pythonCmd + " " + fileToCommandArgument(runProg) + verboseOpt + disableOpt + - " --install-mode install" + " --interactive " + " --change-directory " + fileToCommandArgument(file) diff --git a/src/programflow-visualization/backend/backend.ts b/src/programflow-visualization/backend/backend.ts index cdba9bd5..fde26ba1 100644 --- a/src/programflow-visualization/backend/backend.ts +++ b/src/programflow-visualization/backend/backend.ts @@ -20,9 +20,6 @@ export function startBackend(context: ExtensionContext, file: Uri, outChannel: O break; } - // Get WYPP path - const runYourProgramPath = context.asAbsolutePath("python/src/runYourProgram.py"); - const mainPath = context.asAbsolutePath("pytrace-generator/main.py"); const workerPath = path.resolve(__dirname, 'trace_generator.js'); const worker = new Worker(workerPath); @@ -34,7 +31,6 @@ export function startBackend(context: ExtensionContext, file: Uri, outChannel: O mainPath: mainPath, logPort: logChannel.port1, pythonCmd: pythonCmd, - runYourProgramPath: runYourProgramPath, tracePort: traceChannel.port1 }, [logChannel.port1, traceChannel.port1]); return traceChannel.port2; diff --git a/src/programflow-visualization/backend/trace_generator.ts b/src/programflow-visualization/backend/trace_generator.ts index 5fae4985..ec9f8686 100644 --- a/src/programflow-visualization/backend/trace_generator.ts +++ b/src/programflow-visualization/backend/trace_generator.ts @@ -3,17 +3,13 @@ import { AddressInfo, createServer } from 'net'; import { dirname } from 'path'; import { MessagePort, isMainThread, parentPort } from 'worker_threads'; -function installWypp(pythonCmd: string[], runYourProgramPath: string, callback: (error: ExecFileException | null, stdout: string, stderr: string) => void) { - const args = pythonCmd.slice(1).concat([runYourProgramPath, '--install-mode', 'installOnly']); - execFile(pythonCmd[0], args, { windowsHide: true }, callback); -} - -export function generateTrace(pythonCmd: string[], mainPath: string, filePath: string, tracePort: MessagePort, logPort: MessagePort) { +export function generateTrace(pythonCmd: string[], traceGenPath: string, inputFilePath: string, tracePort: MessagePort, logPort: MessagePort) { + // traceGenPath is something like /Users/swehr/devel/write-your-python-program/pytrace-generator/main.py let childRef: ChildProcessWithoutNullStreams[] = []; let buffer = Buffer.alloc(0); const server = createServer((socket) => { socket.on('data', (data) => { - buffer = Buffer.concat([buffer, data]); + buffer = Buffer.concat([buffer as Uint8Array, data as Uint8Array]); while (buffer.length >= 4) { const dataLen = buffer.readUint32BE(0); if (buffer.length >= 4 + dataLen) { @@ -41,9 +37,12 @@ export function generateTrace(pythonCmd: string[], mainPath: string, filePath: s }); server.listen(0, '127.0.0.1', () => { const port = (server.address() as AddressInfo)?.port; - const traceArgs = [mainPath, filePath, port.toString()]; + const traceArgs = [traceGenPath, inputFilePath, port.toString()]; const args = pythonCmd.slice(1).concat(traceArgs); - const child = spawn(pythonCmd[0], args, { cwd: dirname(filePath), windowsHide: true }); + const child = spawn(pythonCmd[0], args, { + cwd: dirname(inputFilePath), + windowsHide: true + }); childRef.push(child); let err = ""; @@ -79,20 +78,6 @@ if (!isMainThread && parentPort) { initParams.tracePort.close(); return; } - installWypp(initParams.pythonCmd, initParams.runYourProgramPath, (error, stdout, stderr) => { - if (stdout.length > 0) { - initParams.logPort.postMessage(`${stdout}\n`); - } - if (stderr.length > 0) { - initParams.logPort.postMessage(`${stderr}\n`); - } - if (error !== null) { - initParams.logPort.postMessage(`${error}\n`); - initParams.logPort.close(); - initParams.tracePort.close(); - return; - } - generateTrace(initParams.pythonCmd, initParams.mainPath, initParams.file, initParams.tracePort, initParams.logPort); - }); + generateTrace(initParams.pythonCmd, initParams.mainPath, initParams.file, initParams.tracePort, initParams.logPort); }); } From c6f01832d45207e918e37915b3bbd4eb36f39ecd Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 10:20:38 +0200 Subject: [PATCH 52/56] update README and version --- README.md | 45 ++++++++++----------------------------------- package.json | 4 ++-- screenshot.jpg | Bin 199100 -> 0 bytes screenshot.png | Bin 0 -> 329953 bytes screenshot2.png | Bin 0 -> 231807 bytes 5 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 screenshot.jpg create mode 100644 screenshot.png create mode 100644 screenshot2.png diff --git a/README.md b/README.md index e0b7959c..fde41b84 100644 --- a/README.md +++ b/README.md @@ -37,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. @@ -47,7 +52,10 @@ You need an explicit import statement such as `from wypp import *`. Here is a screen shot: -![Screenshot](screenshot.jpg) +![Screenshot](screenshot.png) + +There is also a visualization mode, similar to [Python Tutor](https://pythontutor.com/): +![Screenshot](screenshot2.png) 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 @@ -56,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: @@ -190,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 d0b9d403..f2319b21 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.2", + "version": "2.0.", "publisher": "StefanWehr", "icon": "icon.png", "engines": { - "vscode": "^1.85.0" + "vscode": "^1.94.2" }, "keywords": [ "Python", diff --git a/screenshot.jpg b/screenshot.jpg deleted file mode 100644 index 09d521c072fd2f640755855cf347ab5be66251a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199100 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

;1*{@|d7I+u`K$sk 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

QoRzd*L-ZL5{M8y)PcjCHf@GsH6s+ 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 diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc05612ba21613a859a3b7cb32746cd98c59c20 GIT binary patch literal 329953 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

|#n6OJDF6&0~p(gatW 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

y@F^PTZl*vH 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 literal 0 HcmV?d00001 diff --git a/screenshot2.png b/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..4d591a8aff7b56da88f00625811b2fd44d3b7463 GIT binary patch literal 231807 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 Date: Wed, 24 Sep 2025 10:29:06 +0200 Subject: [PATCH 53/56] changelog and setup for 2.0.0 --- ChangeLog.md | 4 ++++ package.json | 2 +- python/setup.py | 8 ++++++-- python/setup_new.py | 42 ------------------------------------------ 4 files changed, 11 insertions(+), 45 deletions(-) delete mode 100644 python/setup_new.py diff --git a/ChangeLog.md b/ChangeLog.md index 97d96afe..d9be4f54 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ # 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) diff --git a/package.json b/package.json index f2319b21..195b628a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Write Your Python Program!", "description": "A user friendly python environment for beginners", "license": "See license in LICENSE", - "version": "2.0.", + "version": "2.0.0", "publisher": "StefanWehr", "icon": "icon.png", "engines": { diff --git a/python/setup.py b/python/setup.py index 909ca170..0bb8a63d 100644 --- a/python/setup.py +++ b/python/setup.py @@ -31,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'] + 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/setup_new.py b/python/setup_new.py deleted file mode 100644 index b0a43537..00000000 --- a/python/setup_new.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup, find_packages -import os -import json - -TOP_DIR = os.path.join(os.path.dirname(__file__), '..') -VERSION_FILE = 'VERSION' - -def writeVersionFile(): - pkgJson = os.path.join(TOP_DIR, 'package.json') - if os.path.isfile(pkgJson): - with open(pkgJson) as f: - d = json.load(f) - version = d['version'] - with open(VERSION_FILE, 'w') as f: - f.write(version) - -writeVersionFile() - -def readVersion(): - with open(VERSION_FILE) as f: - return f.read().strip() - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setup( - name='wypp', - version=readVersion(), - description='A user-friendly python programming environment for beginners', - long_description=long_description, - long_description_content_type="text/markdown", - 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'] + find_packages("deps/untypy", exclude=['test', 'test.*']), - python_requires='>=3.12.0', - scripts=['wypp'] -) - From b9a55e9d6adcce0def9eb465357747227afc59d0 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 10:32:22 +0200 Subject: [PATCH 54/56] update python version for CI checks --- .github/workflows/github-action-test-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-action-test-python.yml b/.github/workflows/github-action-test-python.yml index db70dcc9..1750a1f4 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 }} From e9fbf9c888ec719b991f859659823ee8f563a107 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 10:47:00 +0200 Subject: [PATCH 55/56] remove more absolute paths from test output --- python/TODO_nowrappers.md | 5 ----- python/code/wypp/stacktrace.py | 1 - python/code/wypp/writeYourProgram.py | 9 +++++---- python/file-test-data/extras/testCheck_ok.out | 8 ++++---- python/file-test-data/extras/testDeepEqBug_ok.out | 2 +- python/file-test-data/extras/testFunEq_ok.out | 2 +- python/file-test-data/extras/testGetSource.err | 4 ---- python/file-test-data/extras/testImpossible.err | 2 +- python/file-test-data/extras/testLiteral1.err | 4 ---- python/file-test-data/extras/testTodo.err | 2 +- 10 files changed, 13 insertions(+), 26 deletions(-) diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md index 14fb86ca..d5352319 100644 --- a/python/TODO_nowrappers.md +++ b/python/TODO_nowrappers.md @@ -1,9 +1,4 @@ -* Python 3.13 and 3.14? -* Installation * Test windows -* README * location matcher for vscode - -* Debug slow startup times * show "@record\nclass C" for record attributes diff --git a/python/code/wypp/stacktrace.py b/python/code/wypp/stacktrace.py index 4372010f..eec485c4 100644 --- a/python/code/wypp/stacktrace.py +++ b/python/code/wypp/stacktrace.py @@ -43,7 +43,6 @@ def isRunpyFrame(frame: types.FrameType) -> bool: def limitTraceback(frameList: list[types.FrameType], extraFrames: list[inspect.FrameInfo], filter: bool) -> traceback.StackSummary: - origFrameList = frameList if filter: # Step 1: remove all frames that appear after the first _call_with_frames_removed endIdx = len(frameList) diff --git a/python/code/wypp/writeYourProgram.py b/python/code/wypp/writeYourProgram.py index 45b7052f..55902409 100644 --- a/python/code/wypp/writeYourProgram.py +++ b/python/code/wypp/writeYourProgram.py @@ -7,6 +7,8 @@ import stacktrace import renderTy import location +import paths +import utils _DEBUG = False def _debug(s): @@ -89,9 +91,7 @@ def formatArg(x): loc = None if caller is None else location.Loc.fromFrameInfo(caller) argStr = ', '.join([formatArg(x) for x in args]) tyStr = f'{name}({argStr})' - raise errors.WyppTypeError.invalidType(tyStr, loc) - #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}]?") + 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 @@ -196,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): diff --git a/python/file-test-data/extras/testCheck_ok.out b/python/file-test-data/extras/testCheck_ok.out index 9be5c707..b4ed27cd 100644 --- a/python/file-test-data/extras/testCheck_ok.out +++ b/python/file-test-data/extras/testCheck_ok.out @@ -1,5 +1,5 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:13: Erwartet wird 2, aber das Ergebnis ist 1 -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc' -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2) -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testCheck_ok.py:16: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Name(firstName='Max', lastName='Müller') +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/file-test-data/extras/testDeepEqBug_ok.out b/python/file-test-data/extras/testDeepEqBug_ok.out index 5f12c81c..bdcb1c6b 100644 --- a/python/file-test-data/extras/testDeepEqBug_ok.out +++ b/python/file-test-data/extras/testDeepEqBug_ok.out @@ -1,2 +1,2 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testDeepEqBug_ok.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'])]) +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/file-test-data/extras/testFunEq_ok.out b/python/file-test-data/extras/testFunEq_ok.out index 5f621b79..7ec80606 100644 --- a/python/file-test-data/extras/testFunEq_ok.out +++ b/python/file-test-data/extras/testFunEq_ok.out @@ -1,2 +1,2 @@ -FEHLER in /Users/swehr/devel/write-your-python-program/python/file-test-data/extras/testFunEq_ok.py:9: Erwartet wird , aber das Ergebnis ist +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/file-test-data/extras/testGetSource.err b/python/file-test-data/extras/testGetSource.err index 3605d652..4a8b3cb7 100644 --- a/python/file-test-data/extras/testGetSource.err +++ b/python/file-test-data/extras/testGetSource.err @@ -1,10 +1,6 @@ Traceback (most recent call last): File "file-test-data/extras/testGetSource.py", line 11, in Art = Literal('klein','mittag') # <= problem is here - File "code/wypp/writeYourProgram.py", line 92, in _invalidCall - raise errors.WyppTypeError.invalidType(tyStr, loc) - File "code/wypp/errors.py", line 79, in invalidType - raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')` diff --git a/python/file-test-data/extras/testImpossible.err b/python/file-test-data/extras/testImpossible.err index de4ec37a..19e40e8d 100644 --- a/python/file-test-data/extras/testImpossible.err +++ b/python/file-test-data/extras/testImpossible.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "file-test-data/extras/testImpossible.py", line 3, in impossible() - File "code/wypp/writeYourProgram.py", line 331, 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/testLiteral1.err b/python/file-test-data/extras/testLiteral1.err index 015899fb..14bb807e 100644 --- a/python/file-test-data/extras/testLiteral1.err +++ b/python/file-test-data/extras/testLiteral1.err @@ -1,10 +1,6 @@ Traceback (most recent call last): File "file-test-data/extras/testLiteral1.py", line 3, in T = Literal('a', 'b') - File "code/wypp/writeYourProgram.py", line 92, in _invalidCall - raise errors.WyppTypeError.invalidType(tyStr, loc) - File "code/wypp/errors.py", line 79, in invalidType - raise WyppTypeError('\n'.join(lines)) WyppTypeError: ungültiger Typ `Literal('a', 'b')` diff --git a/python/file-test-data/extras/testTodo.err b/python/file-test-data/extras/testTodo.err index ae916125..3764a715 100644 --- a/python/file-test-data/extras/testTodo.err +++ b/python/file-test-data/extras/testTodo.err @@ -1,7 +1,7 @@ Traceback (most recent call last): File "file-test-data/extras/testTodo.py", line 3, in todo() - File "code/wypp/writeYourProgram.py", line 326, in todo + File "code/wypp/writeYourProgram.py", line 327, in todo raise errors.TodoError(msg) TODO From a561b1af03cfd9f74f7a915459ba8f9b1d203e43 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 24 Sep 2025 10:51:20 +0200 Subject: [PATCH 56/56] fix trace generator tests --- .github/workflows/github-action-test-python.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/github-action-test-python.yml b/.github/workflows/github-action-test-python.yml index 1750a1f4..46b34cf1 100644 --- a/.github/workflows/github-action-test-python.yml +++ b/.github/workflows/github-action-test-python.yml @@ -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