From e6686269a5ee53d78249eeb447d399fa487e4ad3 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 14 Oct 2026 06:40:28 +0200 Subject: [PATCH 1/4] version bump and changelog for 2.5.0 --- ChangeLog.md | 15 ++++++--------- package.json | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ffa810b..5fca2ca 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,19 +1,16 @@ # Write Your Python Program - CHANGELOG -* unreleased - * RUN button: a program that is still running (and all processes it started) - is terminated before the new run starts. Restarting now also works with - pending input in the interpreter, a program in an endless loop or a program - waiting for `input()` - * RUN button: disables AI features (GitHub Copilot) in the workspace settings - (`chat.disableAIFeatures`) - * Requires VS Code 1.104 or newer +* 2.5.0 (2026-10-14) + * More robust termination of running interpreter when hitting RUN + * Disable AI features on RUN (`chat.disableAIFeatures`) + * Visualization fixes + * Require VS Code 1.104 or newer * 2.4.0 (2026-09-28) * Greatly improved visualizatioin * Python 3.14: forward references in records and functions work without `from __future__ import annotations` * Support for Python 3.15 - * Fix welcome message + * Fix welcome message * 2.3.0 (2026-04-03) * Use relative imports in the whole code base * 2.2.2 (2026-04-03) diff --git a/package.json b/package.json index 1e0c19e..642c2e4 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.4.0", + "version": "2.5.0", "publisher": "StefanWehr", "icon": "icon.png", "engines": { From 34733fae478dc5278aba004929b01e73a708d3ed Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 26 Oct 2026 07:00:50 +0100 Subject: [PATCH 2/4] fix forward refs and some highlighting --- python/code/typeguard/_checkers.py | 4 ++++ python/code/wypp/myTypeguard.py | 12 +++++++++++- python/code/wypp/typecheck.py | 12 ++++++++---- python/file-test-data/basics/constructor.err | 2 +- python/file-test-data/basics/constructor.err_en | 2 +- python/file-test-data/basics/mutable2.err | 2 +- python/file-test-data/basics/mutable2.err_en | 2 +- python/file-test-data/basics/record.err | 2 +- python/file-test-data/basics/record.err_en | 2 +- python/file-test-data/extras/declared-at-missing.err | 2 +- python/file-test-data/extras/testForwardRef5.err | 2 +- python/file-test-data/extras/testRecordTypes.err | 2 +- python/file-test-data/extras/testTypesProtos1.err | 2 +- python/file-test-data/extras/testTypesProtos2.err | 2 +- python/file-test-data/extras/testTypesProtos6.err | 2 +- python/file-test-data/extras/testTypesProtos7.err | 2 +- python/file-test-data/extras/testTypesProtos8.err | 2 +- python/file-test-data/extras/testTypesProtos9.err | 2 +- .../file-test-data/extras/testTypesSubclassing1.err | 2 +- python/fileTests.py | 1 + 20 files changed, 40 insertions(+), 21 deletions(-) diff --git a/python/code/typeguard/_checkers.py b/python/code/typeguard/_checkers.py index 70ee854..3c3ed52 100644 --- a/python/code/typeguard/_checkers.py +++ b/python/code/typeguard/_checkers.py @@ -933,8 +933,12 @@ def check_type_internal( stacklevel=get_stacklevel(), ) return + # The forward reference might refer to a type alias + annotation = resolve_alias_chains(annotation) if isinstance(annotation, str): annotation = resolve_annotation_str(annotation, memo.globals, memo.locals) + # The string might refer to a type alias + annotation = resolve_alias_chains(annotation) if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock): return diff --git a/python/code/wypp/myTypeguard.py b/python/code/wypp/myTypeguard.py index d037c0b..f041e24 100644 --- a/python/code/wypp/myTypeguard.py +++ b/python/code/wypp/myTypeguard.py @@ -23,10 +23,20 @@ class MatchesTyFailure: type MatchesTyResult = bool | MatchesTyFailure -def matchesTy(a: Any, ty: Any, ns: Namespaces) -> MatchesTyResult: +def matchesTy(a: Any, ty: Any, ns: Namespaces, + ignoreUnresolvedForwardRefs: bool = False) -> MatchesTyResult: + # Since python 3.14, annotations may contain ForwardRef objects (see utils.getAnnotations). + # If such a ForwardRef cannot be resolved (e.g. because the type is defined after its + # first use), we want an error, not just a warning. For checks performed at definition time, + # the type might legitimately be defined later, so these checks can ignore such ForwardRefs. + if ignoreUnresolvedForwardRefs: + policy = typeguard.ForwardRefPolicy.IGNORE + else: + policy = typeguard.ForwardRefPolicy.ERROR try: typeguard.check_type(a, ty, + forward_ref_policy=policy, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS, ns=(ns.globals, ns.locals)) return True diff --git a/python/code/wypp/typecheck.py b/python/code/wypp/typecheck.py index 24a5d54..e4a40f3 100644 --- a/python/code/wypp/typecheck.py +++ b/python/code/wypp/typecheck.py @@ -66,7 +66,9 @@ def checkSignature(sig: inspect.Signature, info: location.CallableInfo, cfg: Che raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(info), name, locDecl) if p.default is not inspect.Parameter.empty: locDecl = lambda: info.getParamSourceLocation(name) - if not handleMatchesTyResult(matchesTy(p.default, ty, cfg.ns), locDecl): + # The type of the default value might refer to a type defined later + res = matchesTy(p.default, ty, cfg.ns, ignoreUnresolvedForwardRefs=True) + if not handleMatchesTyResult(res, locDecl): raise errors.WyppTypeError.defaultError(location.CallableName.mk(info), name, locDecl(), ty, p.default) @@ -147,10 +149,11 @@ def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict, def getCallLoc() -> Optional[location.Loc]: fi = stacktrace.callerOutsideWypp() return None if not fi else location.Loc.fromFrameInfo(fi) + # idxOrName is the index of the argument in the call (without self) or the keyword def getLocArg(idxOrName: int | str) -> Callable[[], Optional[location.Loc]]: def f(): fi = stacktrace.callerOutsideWypp() - return None if fi is None else location.locationOfArgument(fi, i) + return None if fi is None else location.locationOfArgument(fi, idxOrName) return f def raiseArgMismatch(): raise errors.WyppTypeError.argCountMismatch(cn, @@ -177,9 +180,10 @@ def raiseArgMismatch(): if i < len(positionalNames): name = positionalNames[i] p = sig.parameters[name] - checkArgument(p, name, i - offset, args[i], getLocArg(i), info, cfg) + checkArgument(p, name, i - offset, args[i], getLocArg(i - offset), info, cfg) elif varPositionalParam is not None: - checkArgument(varPositionalParam, varPositionalParam.name, i - offset, args[i], getLocArg(i), info, cfg) + checkArgument(varPositionalParam, varPositionalParam.name, i - offset, args[i], + getLocArg(i - offset), info, cfg) else: raiseArgMismatch() # Check keyword args diff --git a/python/file-test-data/basics/constructor.err b/python/file-test-data/basics/constructor.err index dcc7b78..d657438 100644 --- a/python/file-test-data/basics/constructor.err +++ b/python/file-test-data/basics/constructor.err @@ -10,7 +10,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/basics/constructor.py ## Fehlerhafter Aufruf in Zeile 5: -c = C("1") +c = C("1") ## Typ deklariert in Zeile 2: diff --git a/python/file-test-data/basics/constructor.err_en b/python/file-test-data/basics/constructor.err_en index 44f7d2c..86bd1ef 100644 --- a/python/file-test-data/basics/constructor.err_en +++ b/python/file-test-data/basics/constructor.err_en @@ -10,7 +10,7 @@ But the value given has type `str`. ## File file-test-data/basics/constructor.py ## Problematic call in line 5: -c = C("1") +c = C("1") ## Type declared in line 2: diff --git a/python/file-test-data/basics/mutable2.err b/python/file-test-data/basics/mutable2.err index 350e0a0..c21449e 100644 --- a/python/file-test-data/basics/mutable2.err +++ b/python/file-test-data/basics/mutable2.err @@ -10,7 +10,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/basics/mutable2.py ## Fehlerhafter Aufruf in Zeile 8: -p = Point(1, '2') +p = Point(1, '2') ## Typ deklariert in Zeile 6: diff --git a/python/file-test-data/basics/mutable2.err_en b/python/file-test-data/basics/mutable2.err_en index 515ba4e..2046d70 100644 --- a/python/file-test-data/basics/mutable2.err_en +++ b/python/file-test-data/basics/mutable2.err_en @@ -10,7 +10,7 @@ But the value given has type `str`. ## File file-test-data/basics/mutable2.py ## Problematic call in line 8: -p = Point(1, '2') +p = Point(1, '2') ## Type declared in line 6: diff --git a/python/file-test-data/basics/record.err b/python/file-test-data/basics/record.err index 59f5f15..18d0c1a 100644 --- a/python/file-test-data/basics/record.err +++ b/python/file-test-data/basics/record.err @@ -10,7 +10,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/basics/record.py ## Fehlerhafter Aufruf in Zeile 8: -p = Person("Alice", "30") +p = Person("Alice", "30") ## Typ deklariert in Zeile 6: diff --git a/python/file-test-data/basics/record.err_en b/python/file-test-data/basics/record.err_en index d3e78da..bc2c2d2 100644 --- a/python/file-test-data/basics/record.err_en +++ b/python/file-test-data/basics/record.err_en @@ -10,7 +10,7 @@ But the value given has type `str`. ## File file-test-data/basics/record.py ## Problematic call in line 8: -p = Person("Alice", "30") +p = Person("Alice", "30") ## Type declared in line 6: diff --git a/python/file-test-data/extras/declared-at-missing.err b/python/file-test-data/extras/declared-at-missing.err index bbc840c..db508f9 100644 --- a/python/file-test-data/extras/declared-at-missing.err +++ b/python/file-test-data/extras/declared-at-missing.err @@ -9,7 +9,7 @@ Der Aufruf des Konstruktors des Records `Semester` erwartet einen Wert vom Typ ` ## Datei file-test-data/extras/declared-at-missing.py ## Fehlerhafter Aufruf in Zeile 22: -semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) +semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) ## Typ deklariert in Zeile 19: diff --git a/python/file-test-data/extras/testForwardRef5.err b/python/file-test-data/extras/testForwardRef5.err index 346b2d0..1b164b9 100644 --- a/python/file-test-data/extras/testForwardRef5.err +++ b/python/file-test-data/extras/testForwardRef5.err @@ -9,7 +9,7 @@ Der Aufruf des Konstruktors des Records `Garage` erwartet einen Wert vom Typ `li ## Datei file-test-data/extras/testForwardRef5.py ## Fehlerhafter Aufruf in Zeile 22: -garage = Garage(cars=[Car(color='red'), "Not A Car"]) +garage = Garage(cars=[Car(color='red'), "Not A Car"]) ## Typ deklariert in Zeile 11: diff --git a/python/file-test-data/extras/testRecordTypes.err b/python/file-test-data/extras/testRecordTypes.err index 6354126..6bd0efa 100644 --- a/python/file-test-data/extras/testRecordTypes.err +++ b/python/file-test-data/extras/testRecordTypes.err @@ -10,7 +10,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/extras/testRecordTypes.py ## Fehlerhafter Aufruf in Zeile 8: -p = Point(1, '5') +p = Point(1, '5') ## Typ deklariert in Zeile 6: diff --git a/python/file-test-data/extras/testTypesProtos1.err b/python/file-test-data/extras/testTypesProtos1.err index 7b226b1..4499fc5 100644 --- a/python/file-test-data/extras/testTypesProtos1.err +++ b/python/file-test-data/extras/testTypesProtos1.err @@ -12,7 +12,7 @@ Aber der übergebene Wert hat den Typ `float`. ## Datei file-test-data/extras/testTypesProtos1.py ## Fehlerhafter Aufruf in Zeile 19: - print(a.makeSound(3.14)) + print(a.makeSound(3.14)) ## Typ deklariert in Zeile 13: diff --git a/python/file-test-data/extras/testTypesProtos2.err b/python/file-test-data/extras/testTypesProtos2.err index 154ffd2..9e30dd8 100644 --- a/python/file-test-data/extras/testTypesProtos2.err +++ b/python/file-test-data/extras/testTypesProtos2.err @@ -12,7 +12,7 @@ Aber der übergebene Wert hat den Typ `float`. ## Datei file-test-data/extras/testTypesProtos2.py ## Fehlerhafter Aufruf in Zeile 19: - print(a.makeSound(3.14)) + print(a.makeSound(3.14)) ## Typ deklariert in Zeile 13: diff --git a/python/file-test-data/extras/testTypesProtos6.err b/python/file-test-data/extras/testTypesProtos6.err index b064562..0b9cc98 100644 --- a/python/file-test-data/extras/testTypesProtos6.err +++ b/python/file-test-data/extras/testTypesProtos6.err @@ -18,7 +18,7 @@ Aber der übergebene Wert hat den Typ `File`. ## Datei file-test-data/extras/testTypesProtos6.py ## Fehlerhafter Aufruf in Zeile 30: - visitor.visitFile(self) + visitor.visitFile(self) ## Typ deklariert in Zeile 44: diff --git a/python/file-test-data/extras/testTypesProtos7.err b/python/file-test-data/extras/testTypesProtos7.err index 4410e5b..59305ae 100644 --- a/python/file-test-data/extras/testTypesProtos7.err +++ b/python/file-test-data/extras/testTypesProtos7.err @@ -18,7 +18,7 @@ Aber der übergebene Wert hat den Typ `File`. ## Datei file-test-data/extras/testTypesProtos7.py ## Fehlerhafter Aufruf in Zeile 49: - visitor.visitFile(self) + visitor.visitFile(self) ## Typ deklariert in Zeile 63: diff --git a/python/file-test-data/extras/testTypesProtos8.err b/python/file-test-data/extras/testTypesProtos8.err index 23b06b5..798dea3 100644 --- a/python/file-test-data/extras/testTypesProtos8.err +++ b/python/file-test-data/extras/testTypesProtos8.err @@ -12,7 +12,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/extras/testTypesProtos8.py ## Fehlerhafter Aufruf in Zeile 10: - b.foo(1, "foo") + b.foo(1, "foo") ## Typ deklariert in Zeile 6: diff --git a/python/file-test-data/extras/testTypesProtos9.err b/python/file-test-data/extras/testTypesProtos9.err index 95e2d77..4a3ceaa 100644 --- a/python/file-test-data/extras/testTypesProtos9.err +++ b/python/file-test-data/extras/testTypesProtos9.err @@ -12,7 +12,7 @@ Aber der übergebene Wert hat den Typ `str`. ## Datei file-test-data/extras/testTypesProtos9.py ## Fehlerhafter Aufruf in Zeile 10: - b.foo(1, "foo") + b.foo(1, "foo") ## Typ deklariert in Zeile 6: diff --git a/python/file-test-data/extras/testTypesSubclassing1.err b/python/file-test-data/extras/testTypesSubclassing1.err index 25ec705..48423af 100644 --- a/python/file-test-data/extras/testTypesSubclassing1.err +++ b/python/file-test-data/extras/testTypesSubclassing1.err @@ -12,7 +12,7 @@ Aber der übergebene Wert hat den Typ `AnimalFood`. ## Datei file-test-data/extras/testTypesSubclassing1.py ## Fehlerhafter Aufruf in Zeile 26: - a.feed(AnimalFood('some cat food')) + a.feed(AnimalFood('some cat food')) ## Typ deklariert in Zeile 22: diff --git a/python/fileTests.py b/python/fileTests.py index c6662e7..b3106a2 100644 --- a/python/fileTests.py +++ b/python/fileTests.py @@ -14,6 +14,7 @@ def pythonMaxVersion(major: int, minor: int) -> bool: special = { 'file-test-data/basics/recursive_ok.py': pythonMinVersion(3, 14), + 'file-test-data/basics/forwardRefs3_ok.py': pythonMinVersion(3, 14), 'file-test-data/basics/recursive_old_fail.py': pythonMaxVersion(3, 13) } From b24c827670b88c47db968678218bc96f16d6a025 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 26 Oct 2026 17:16:53 +0100 Subject: [PATCH 3/4] new tests --- python/file-test-data/basics/argLocation1.err | 17 ++++++ .../file-test-data/basics/argLocation1.err_en | 17 ++++++ python/file-test-data/basics/argLocation1.py | 6 +++ python/file-test-data/basics/argLocation2.err | 17 ++++++ .../file-test-data/basics/argLocation2.err_en | 17 ++++++ python/file-test-data/basics/argLocation2.py | 10 ++++ python/file-test-data/basics/argLocation3.err | 17 ++++++ .../file-test-data/basics/argLocation3.err_en | 17 ++++++ python/file-test-data/basics/argLocation3.py | 5 ++ python/file-test-data/basics/forwardRefs2.err | 10 ++++ .../file-test-data/basics/forwardRefs2.err_en | 10 ++++ python/file-test-data/basics/forwardRefs2.py | 13 +++++ .../file-test-data/basics/forwardRefs3_ok.out | 4 ++ .../file-test-data/basics/forwardRefs3_ok.py | 19 +++++++ .../file-test-data/basics/forwardRefs4_ok.out | 1 + .../basics/forwardRefs4_ok.out_en | 1 + .../file-test-data/basics/forwardRefs4_ok.py | 54 +++++++++++++++++++ python/file-test-data/basics/forwardRefs5.err | 17 ++++++ .../file-test-data/basics/forwardRefs5.err_en | 17 ++++++ python/file-test-data/basics/forwardRefs5.py | 14 +++++ python/run | 2 + 21 files changed, 285 insertions(+) create mode 100644 python/file-test-data/basics/argLocation1.err create mode 100644 python/file-test-data/basics/argLocation1.err_en create mode 100644 python/file-test-data/basics/argLocation1.py create mode 100644 python/file-test-data/basics/argLocation2.err create mode 100644 python/file-test-data/basics/argLocation2.err_en create mode 100644 python/file-test-data/basics/argLocation2.py create mode 100644 python/file-test-data/basics/argLocation3.err create mode 100644 python/file-test-data/basics/argLocation3.err_en create mode 100644 python/file-test-data/basics/argLocation3.py create mode 100644 python/file-test-data/basics/forwardRefs2.err create mode 100644 python/file-test-data/basics/forwardRefs2.err_en create mode 100644 python/file-test-data/basics/forwardRefs2.py create mode 100644 python/file-test-data/basics/forwardRefs3_ok.out create mode 100644 python/file-test-data/basics/forwardRefs3_ok.py create mode 100644 python/file-test-data/basics/forwardRefs4_ok.out create mode 100644 python/file-test-data/basics/forwardRefs4_ok.out_en create mode 100644 python/file-test-data/basics/forwardRefs4_ok.py create mode 100644 python/file-test-data/basics/forwardRefs5.err create mode 100644 python/file-test-data/basics/forwardRefs5.err_en create mode 100644 python/file-test-data/basics/forwardRefs5.py diff --git a/python/file-test-data/basics/argLocation1.err b/python/file-test-data/basics/argLocation1.err new file mode 100644 index 0000000..9317460 --- /dev/null +++ b/python/file-test-data/basics/argLocation1.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation1.py", line 6, in + x = C().m(1, 'zwei', 3) + +WyppTypeError: 'zwei' + +Der Aufruf der Methode `m` der Klasse `C` erwartet einen Wert vom Typ `int` als zweites Argument. +Aber der übergebene Wert hat den Typ `str`. + +## Datei file-test-data/basics/argLocation1.py +## Fehlerhafter Aufruf in Zeile 6: + +x = C().m(1, 'zwei', 3) + +## Typ deklariert in Zeile 2: + + def m(self, a: int, b: int, c: int) -> int: diff --git a/python/file-test-data/basics/argLocation1.err_en b/python/file-test-data/basics/argLocation1.err_en new file mode 100644 index 0000000..91f7f13 --- /dev/null +++ b/python/file-test-data/basics/argLocation1.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation1.py", line 6, in + x = C().m(1, 'zwei', 3) + +WyppTypeError: 'zwei' + +The call of method `m` of class `C` expects value of type `int` as 2nd argument. +But the value given has type `str`. + +## File file-test-data/basics/argLocation1.py +## Problematic call in line 6: + +x = C().m(1, 'zwei', 3) + +## Type declared in line 2: + + def m(self, a: int, b: int, c: int) -> int: diff --git a/python/file-test-data/basics/argLocation1.py b/python/file-test-data/basics/argLocation1.py new file mode 100644 index 0000000..9996a0a --- /dev/null +++ b/python/file-test-data/basics/argLocation1.py @@ -0,0 +1,6 @@ +class C: + def m(self, a: int, b: int, c: int) -> int: + return a + b + c + +# The second argument must be highlighted +x = C().m(1, 'zwei', 3) diff --git a/python/file-test-data/basics/argLocation2.err b/python/file-test-data/basics/argLocation2.err new file mode 100644 index 0000000..0d03ed7 --- /dev/null +++ b/python/file-test-data/basics/argLocation2.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation2.py", line 10, in + p = Point(1, z=3, y='zwei') + +WyppTypeError: 'zwei' + +Der Aufruf des Konstruktors des Records `Point` erwartet einen Wert vom Typ `int` als Argument `y`. +Aber der übergebene Wert hat den Typ `str`. + +## Datei file-test-data/basics/argLocation2.py +## Fehlerhafter Aufruf in Zeile 10: + +p = Point(1, z=3, y='zwei') + +## Typ deklariert in Zeile 6: + + y: int diff --git a/python/file-test-data/basics/argLocation2.err_en b/python/file-test-data/basics/argLocation2.err_en new file mode 100644 index 0000000..19dc430 --- /dev/null +++ b/python/file-test-data/basics/argLocation2.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation2.py", line 10, in + p = Point(1, z=3, y='zwei') + +WyppTypeError: 'zwei' + +The call of the constructor of record `Point` expects value of type `int` as argument `y`. +But the value given has type `str`. + +## File file-test-data/basics/argLocation2.py +## Problematic call in line 10: + +p = Point(1, z=3, y='zwei') + +## Type declared in line 6: + + y: int diff --git a/python/file-test-data/basics/argLocation2.py b/python/file-test-data/basics/argLocation2.py new file mode 100644 index 0000000..a4b6534 --- /dev/null +++ b/python/file-test-data/basics/argLocation2.py @@ -0,0 +1,10 @@ +from wypp import * + +@record +class Point: + x: int + y: int + z: int + +# The keyword argument y must be highlighted +p = Point(1, z=3, y='zwei') diff --git a/python/file-test-data/basics/argLocation3.err b/python/file-test-data/basics/argLocation3.err new file mode 100644 index 0000000..3b42297 --- /dev/null +++ b/python/file-test-data/basics/argLocation3.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation3.py", line 5, in + z = foo(y=2, x='eins') + +WyppTypeError: 'eins' + +Der Aufruf der Funktion `foo` erwartet einen Wert vom Typ `int` als Argument `x`. +Aber der übergebene Wert hat den Typ `str`. + +## Datei file-test-data/basics/argLocation3.py +## Fehlerhafter Aufruf in Zeile 5: + +z = foo(y=2, x='eins') + +## Typ deklariert in Zeile 1: + +def foo(x: int, y: int) -> int: diff --git a/python/file-test-data/basics/argLocation3.err_en b/python/file-test-data/basics/argLocation3.err_en new file mode 100644 index 0000000..0d7a93f --- /dev/null +++ b/python/file-test-data/basics/argLocation3.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/argLocation3.py", line 5, in + z = foo(y=2, x='eins') + +WyppTypeError: 'eins' + +The call of function `foo` expects value of type `int` as argument `x`. +But the value given has type `str`. + +## File file-test-data/basics/argLocation3.py +## Problematic call in line 5: + +z = foo(y=2, x='eins') + +## Type declared in line 1: + +def foo(x: int, y: int) -> int: diff --git a/python/file-test-data/basics/argLocation3.py b/python/file-test-data/basics/argLocation3.py new file mode 100644 index 0000000..49df36c --- /dev/null +++ b/python/file-test-data/basics/argLocation3.py @@ -0,0 +1,5 @@ +def foo(x: int, y: int) -> int: + return x + y + +# Only keyword arguments, the keyword argument x must be highlighted +z = foo(y=2, x='eins') diff --git a/python/file-test-data/basics/forwardRefs2.err b/python/file-test-data/basics/forwardRefs2.err new file mode 100644 index 0000000..d8ce859 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs2.err @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "file-test-data/basics/forwardRefs2.py", line 11, in + paula = Papagei('mittel', "Let's go to the punkrock show") + +WyppTypeError: ungültiger Typ `Gewichtsklasse` + +## Datei file-test-data/basics/forwardRefs2.py +## Typ deklariert in Zeile 8: + + gewichtsklasse: Gewichtsklasse diff --git a/python/file-test-data/basics/forwardRefs2.err_en b/python/file-test-data/basics/forwardRefs2.err_en new file mode 100644 index 0000000..2538e4b --- /dev/null +++ b/python/file-test-data/basics/forwardRefs2.err_en @@ -0,0 +1,10 @@ +Traceback (most recent call last): + File "file-test-data/basics/forwardRefs2.py", line 11, in + paula = Papagei('mittel', "Let's go to the punkrock show") + +WyppTypeError: invalid type `Gewichtsklasse` + +## File file-test-data/basics/forwardRefs2.py +## Type declared in line 8: + + gewichtsklasse: Gewichtsklasse diff --git a/python/file-test-data/basics/forwardRefs2.py b/python/file-test-data/basics/forwardRefs2.py new file mode 100644 index 0000000..c1eaaa3 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs2.py @@ -0,0 +1,13 @@ +# from __future__ import annotations +# Leave the comment, it's needed for tests with python versions <= 3.13 +from wypp import * + +# The type Gewichtsklasse is used before it is defined +@record +class Papagei: + gewichtsklasse: Gewichtsklasse + satz: str + +paula = Papagei('mittel', "Let's go to the punkrock show") + +type Gewichtsklasse = Literal['leicht', 'mittel', 'schwer'] diff --git a/python/file-test-data/basics/forwardRefs3_ok.out b/python/file-test-data/basics/forwardRefs3_ok.out new file mode 100644 index 0000000..8764c27 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs3_ok.out @@ -0,0 +1,4 @@ +Bar(x=1, other=None) +Bar(x=1, other=Baz(y=2)) +1 +1 diff --git a/python/file-test-data/basics/forwardRefs3_ok.py b/python/file-test-data/basics/forwardRefs3_ok.py new file mode 100644 index 0000000..feda49f --- /dev/null +++ b/python/file-test-data/basics/forwardRefs3_ok.py @@ -0,0 +1,19 @@ +from wypp import * + +# The types of the default values refer to a type defined later +@record +class Bar: + x: int + other: Optional[Baz] = None + +def f(b: Optional[Baz] = None) -> int: + return 1 + +@record +class Baz: + y: int + +print(Bar(1)) +print(Bar(1, Baz(2))) +print(f()) +print(f(Baz(3))) diff --git a/python/file-test-data/basics/forwardRefs4_ok.out b/python/file-test-data/basics/forwardRefs4_ok.out new file mode 100644 index 0000000..07e789c --- /dev/null +++ b/python/file-test-data/basics/forwardRefs4_ok.out @@ -0,0 +1 @@ +4 Tests, alle erfolgreich 😀 diff --git a/python/file-test-data/basics/forwardRefs4_ok.out_en b/python/file-test-data/basics/forwardRefs4_ok.out_en new file mode 100644 index 0000000..c5d1fbf --- /dev/null +++ b/python/file-test-data/basics/forwardRefs4_ok.out_en @@ -0,0 +1 @@ +4 tests, all successful 😀 diff --git a/python/file-test-data/basics/forwardRefs4_ok.py b/python/file-test-data/basics/forwardRefs4_ok.py new file mode 100644 index 0000000..36e0d14 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs4_ok.py @@ -0,0 +1,54 @@ +# from __future__ import annotations +# Leave the comment, it's needed for tests with python versions <= 3.13 +from wypp import * + +# Der Lebensstatus ist entweder tot oder lebendig +type Lebensstatus = Literal['tot', 'lebendig'] + +# Ein Gürteltiert hat folgende Eigenschaften +# - Lebensstatus: tot oder lebendig (Typ: Lebensstatus) +# - Gewicht in Gramm (Typ: int) +@record +class Gürteltier: + lebenstatus: Lebensstatus + gewicht: int + +# Ein Tier ist eins der folgenden: +# - Gürteltier (Typ: Gürteltier) oder +# - Papagei (Typ: Papagei) +type Tier = Union[Gürteltier, Papagei] + +# Ein Papagei hat folgende Eigenschaften: +# - Gewichtsklasse (Typ: Gewichtsklasse) +# - Satz den er sagt (Typ: string) +@record +class Papagei: + gewichtsklasse: Gewichtsklasse + satz: str + +# Die Gewichtsklasse eines Papageis ist leicht, mittel oder schwer +type Gewichtsklasse = Literal['leicht', 'mittel', 'schwer'] + +# Funktion zur Bestimmung des Gewichts von Tieren +# Eingabe: ein Tier (Typ: Tier) +# Ergebnis: Gewicht in Gramm (Typ: int) +def wiegeTier(tier: Tier) -> int: + if isinstance(tier, Gürteltier): + return tier.gewicht + elif tier.gewichtsklasse == 'leicht': + return 900 + elif tier.gewichtsklasse == 'mittel': + return 1200 + else: + return 1500 + +# All types are defined at this point +dora = Gürteltier('lebendig', 25000) +daniel = Gürteltier('tot', 30000) +paula = Papagei('mittel', "Let's go to the punkrock show") +peter = Papagei('leicht', 'oh not, not again') + +check(wiegeTier(dora), 25000) +check(wiegeTier(daniel), 30000) +check(wiegeTier(paula), 1200) +check(wiegeTier(peter), 900) diff --git a/python/file-test-data/basics/forwardRefs5.err b/python/file-test-data/basics/forwardRefs5.err new file mode 100644 index 0000000..ef70112 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs5.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/forwardRefs5.py", line 14, in + peter = Papagei('superschwer', 'oh not, not again') + +WyppTypeError: 'superschwer' + +Der Aufruf des Konstruktors des Records `Papagei` erwartet einen Wert vom Typ `Gewichtsklasse` als erstes Argument. +Aber der übergebene Wert hat den Typ `str`. + +## Datei file-test-data/basics/forwardRefs5.py +## Fehlerhafter Aufruf in Zeile 14: + +peter = Papagei('superschwer', 'oh not, not again') + +## Typ deklariert in Zeile 8: + + gewichtsklasse: Gewichtsklasse diff --git a/python/file-test-data/basics/forwardRefs5.err_en b/python/file-test-data/basics/forwardRefs5.err_en new file mode 100644 index 0000000..f3da324 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs5.err_en @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/forwardRefs5.py", line 14, in + peter = Papagei('superschwer', 'oh not, not again') + +WyppTypeError: 'superschwer' + +The call of the constructor of record `Papagei` expects value of type `Gewichtsklasse` as 1st argument. +But the value given has type `str`. + +## File file-test-data/basics/forwardRefs5.py +## Problematic call in line 14: + +peter = Papagei('superschwer', 'oh not, not again') + +## Type declared in line 8: + + gewichtsklasse: Gewichtsklasse diff --git a/python/file-test-data/basics/forwardRefs5.py b/python/file-test-data/basics/forwardRefs5.py new file mode 100644 index 0000000..cff66c0 --- /dev/null +++ b/python/file-test-data/basics/forwardRefs5.py @@ -0,0 +1,14 @@ +# from __future__ import annotations +# Leave the comment, it's needed for tests with python versions <= 3.13 +from wypp import * + +# The type Gewichtsklasse is defined after the record but before its first use +@record +class Papagei: + gewichtsklasse: Gewichtsklasse + satz: str + +type Gewichtsklasse = Literal['leicht', 'mittel', 'schwer'] + +paula = Papagei('mittel', "Let's go to the punkrock show") +peter = Papagei('superschwer', 'oh not, not again') diff --git a/python/run b/python/run index 06e6f6b..5e603ae 100755 --- a/python/run +++ b/python/run @@ -11,6 +11,8 @@ OPTS="--quiet" # For profiling, uncomment the line below # PROF="-m cProfile -o profile.out" +# echo "Python interpreter: $PY" + $PY $PROF "$SCRIPT_DIR"/code/wypp/runYourProgram.py \ --no-clear $OPTS "$@" exit $? From 3fcda5cdb4fced667d9fd37b91387b2e206cce68 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Mon, 26 Oct 2026 17:39:46 +0100 Subject: [PATCH 4/4] show whole record on error --- python/code/wypp/errors.py | 7 ++++--- python/code/wypp/location.py | 18 +++++++++++++--- python/code/wypp/parsecache.py | 17 +++++++++++++++ python/file-test-data/basics/argLocation2.err | 4 ++++ .../file-test-data/basics/argLocation2.err_en | 4 ++++ python/file-test-data/basics/forwardRefs2.err | 3 +++ .../file-test-data/basics/forwardRefs2.err_en | 3 +++ python/file-test-data/basics/forwardRefs5.err | 3 +++ .../file-test-data/basics/forwardRefs5.err_en | 3 +++ python/file-test-data/basics/mutable.err | 3 +++ python/file-test-data/basics/mutable.err_en | 3 +++ python/file-test-data/basics/mutable2.err | 3 +++ python/file-test-data/basics/mutable2.err_en | 3 +++ python/file-test-data/basics/optionalAttr.err | 3 +++ .../file-test-data/basics/optionalAttr.err_en | 3 +++ python/file-test-data/basics/record.err | 3 +++ python/file-test-data/basics/record.err_en | 3 +++ python/file-test-data/basics/recordDecl.err | 21 +++++++++++++++++++ .../file-test-data/basics/recordDecl.err_en | 21 +++++++++++++++++++ python/file-test-data/basics/recordDecl.py | 15 +++++++++++++ .../extras/declared-at-missing.err | 4 ++++ .../file-test-data/extras/testForwardRef4.err | 2 ++ .../file-test-data/extras/testForwardRef5.err | 2 ++ .../extras/testRecordSetTypeForwardRef.err | 2 ++ .../extras/testRecordSetTypes.err | 5 ++++- .../file-test-data/extras/testRecordTypes.err | 3 +++ .../extras/testTypesRecordInheritance.err | 2 ++ 27 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 python/file-test-data/basics/recordDecl.err create mode 100644 python/file-test-data/basics/recordDecl.err_en create mode 100644 python/file-test-data/basics/recordDecl.py diff --git a/python/code/wypp/errors.py b/python/code/wypp/errors.py index 82751b4..6c60d63 100644 --- a/python/code/wypp/errors.py +++ b/python/code/wypp/errors.py @@ -11,8 +11,8 @@ 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)]) +def renderLoc(loc: location.Loc, withContext: bool = True) -> str: + s = '\n'.join([l.highlight() for l in location.highlightedLines(loc, withContext)]) return s.rstrip() def renderStr(s: str, quote: str): @@ -21,7 +21,8 @@ def renderStr(s: str, quote: str): def renderGiven(givenValue: Any, givenLoc: Optional[location.Loc]) -> str: if type(givenValue) == str and givenLoc is not None: - code = renderLoc(givenLoc) + # Only the code at the location itself determines the quotes + code = renderLoc(givenLoc, withContext=False) single = renderStr(givenValue, "'") double = renderStr(givenValue, '"') if double in code and single not in code: diff --git a/python/code/wypp/location.py b/python/code/wypp/location.py index 7100676..9a9ef0a 100644 --- a/python/code/wypp/location.py +++ b/python/code/wypp/location.py @@ -82,6 +82,9 @@ class Loc: startCol: Optional[int] endLine: Optional[int] endCol: Optional[int] + # Range of lines (inclusive) shown around the location when rendering it. For example, + # the location of a record attribute is shown together with the whole record. + contextLines: Optional[tuple[int, int]] = None def __post_init__(self): self.filename = paths.canonicalizePath(self.filename) @@ -156,11 +159,11 @@ def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv') -> str else: return self.line.decoded() -def highlightedLines(loc: Loc) -> list[SourceLine]: +def highlightedLines(loc: Loc, withContext: bool = True) -> list[SourceLine]: match loc.fullSpan(): case None: line = getline(loc.filename, loc.startLine) - return [SourceLine(line, None)] + result = [SourceLine(line, None)] case (startLine, startCol, endLine, endCol): result = [] for lineNo in range(startLine, startLine+1): @@ -169,6 +172,14 @@ def highlightedLines(loc: Loc) -> list[SourceLine]: c1 = startCol if lineNo == startLine else leadingSpaces c2 = endCol if lineNo == endLine else len(line) result.append(SourceLine(line, (c1, c2))) + match loc.contextLines: + case (first, last) if withContext: + def plain(lineNos: range) -> list[SourceLine]: + return [SourceLine(getline(loc.filename, n), None) for n in lineNos] + before = plain(range(first, loc.startLine)) + after = plain(range(loc.startLine + len(result), last + 1)) + return before + result + after + case _: return result @dataclass @@ -324,7 +335,8 @@ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]: 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) + return Loc(file, node.lineno, node.col_offset, node.end_lineno, node.end_col_offset, + contextLines=ast.getRecordLineRange(self.name)) else: return None @property diff --git a/python/code/wypp/parsecache.py b/python/code/wypp/parsecache.py index d5e7734..b46ab0b 100644 --- a/python/code/wypp/parsecache.py +++ b/python/code/wypp/parsecache.py @@ -116,6 +116,23 @@ def getRecordAttr(self, clsName: str, attrName: str) -> ast.AnnAssign | None: self.__cache[key] = annNode return annNode + def getRecordLineRange(self, clsName: str) -> tuple[int, int] | None: + """ + Returns the range of lines (inclusive) of the toplevel class clsName, starting with + its first decorator and ending with its last attribute of the format `A: T` or + `A: T = ...`. Returns None if no such class or multiple classes are found. + """ + self.__ensureTree() + targetCls = self._findClass(clsName) + if not targetCls: + return None + firstLine = min([targetCls.lineno] + [d.lineno for d in targetCls.decorator_list]) + lastLine = targetCls.lineno + for stmt in targetCls.body: + if isinstance(stmt, ast.AnnAssign) and stmt.end_lineno is not None: + lastLine = max(lastLine, stmt.end_lineno) + return (firstLine, lastLine) + 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 diff --git a/python/file-test-data/basics/argLocation2.err b/python/file-test-data/basics/argLocation2.err index 0d03ed7..0724d92 100644 --- a/python/file-test-data/basics/argLocation2.err +++ b/python/file-test-data/basics/argLocation2.err @@ -14,4 +14,8 @@ p = Point(1, z=3, y='zwei') ## Typ deklariert in Zeile 6: +@record +class Point: + x: int y: int + z: int diff --git a/python/file-test-data/basics/argLocation2.err_en b/python/file-test-data/basics/argLocation2.err_en index 19dc430..4b5ccc3 100644 --- a/python/file-test-data/basics/argLocation2.err_en +++ b/python/file-test-data/basics/argLocation2.err_en @@ -14,4 +14,8 @@ p = Point(1, z=3, y='zwei') ## Type declared in line 6: +@record +class Point: + x: int y: int + z: int diff --git a/python/file-test-data/basics/forwardRefs2.err b/python/file-test-data/basics/forwardRefs2.err index d8ce859..0ba5177 100644 --- a/python/file-test-data/basics/forwardRefs2.err +++ b/python/file-test-data/basics/forwardRefs2.err @@ -7,4 +7,7 @@ WyppTypeError: ungültiger Typ `Gewichtsklasse` ## Datei file-test-data/basics/forwardRefs2.py ## Typ deklariert in Zeile 8: +@record +class Papagei: gewichtsklasse: Gewichtsklasse + satz: str diff --git a/python/file-test-data/basics/forwardRefs2.err_en b/python/file-test-data/basics/forwardRefs2.err_en index 2538e4b..3f1d9a3 100644 --- a/python/file-test-data/basics/forwardRefs2.err_en +++ b/python/file-test-data/basics/forwardRefs2.err_en @@ -7,4 +7,7 @@ WyppTypeError: invalid type `Gewichtsklasse` ## File file-test-data/basics/forwardRefs2.py ## Type declared in line 8: +@record +class Papagei: gewichtsklasse: Gewichtsklasse + satz: str diff --git a/python/file-test-data/basics/forwardRefs5.err b/python/file-test-data/basics/forwardRefs5.err index ef70112..051b199 100644 --- a/python/file-test-data/basics/forwardRefs5.err +++ b/python/file-test-data/basics/forwardRefs5.err @@ -14,4 +14,7 @@ peter = Papagei('superschwer', 'oh not, not again') ## Typ deklariert in Zeile 8: +@record +class Papagei: gewichtsklasse: Gewichtsklasse + satz: str diff --git a/python/file-test-data/basics/forwardRefs5.err_en b/python/file-test-data/basics/forwardRefs5.err_en index f3da324..b85268c 100644 --- a/python/file-test-data/basics/forwardRefs5.err_en +++ b/python/file-test-data/basics/forwardRefs5.err_en @@ -14,4 +14,7 @@ peter = Papagei('superschwer', 'oh not, not again') ## Type declared in line 8: +@record +class Papagei: gewichtsklasse: Gewichtsklasse + satz: str diff --git a/python/file-test-data/basics/mutable.err b/python/file-test-data/basics/mutable.err index 08bc1f2..505a583 100644 --- a/python/file-test-data/basics/mutable.err +++ b/python/file-test-data/basics/mutable.err @@ -14,4 +14,7 @@ p.y = "foo" ## Typ deklariert in Zeile 6: +@record(mutable=True) +class Point: + x: int y: int \ No newline at end of file diff --git a/python/file-test-data/basics/mutable.err_en b/python/file-test-data/basics/mutable.err_en index d5d3c56..2843ae2 100644 --- a/python/file-test-data/basics/mutable.err_en +++ b/python/file-test-data/basics/mutable.err_en @@ -14,4 +14,7 @@ p.y = "foo" ## Type declared in line 6: +@record(mutable=True) +class Point: + x: int y: int \ No newline at end of file diff --git a/python/file-test-data/basics/mutable2.err b/python/file-test-data/basics/mutable2.err index c21449e..313a65f 100644 --- a/python/file-test-data/basics/mutable2.err +++ b/python/file-test-data/basics/mutable2.err @@ -14,4 +14,7 @@ p = Point(1, '2') ## Typ deklariert in Zeile 6: +@record(mutable=True) +class Point: + x: int y: int \ No newline at end of file diff --git a/python/file-test-data/basics/mutable2.err_en b/python/file-test-data/basics/mutable2.err_en index 2046d70..9122c46 100644 --- a/python/file-test-data/basics/mutable2.err_en +++ b/python/file-test-data/basics/mutable2.err_en @@ -14,4 +14,7 @@ p = Point(1, '2') ## Type declared in line 6: +@record(mutable=True) +class Point: + x: int y: int \ No newline at end of file diff --git a/python/file-test-data/basics/optionalAttr.err b/python/file-test-data/basics/optionalAttr.err index f138365..aca5be5 100644 --- a/python/file-test-data/basics/optionalAttr.err +++ b/python/file-test-data/basics/optionalAttr.err @@ -10,4 +10,7 @@ Aber der Default-Wert hat den Typ `str`. ## Datei file-test-data/basics/optionalAttr.py ## Parameter deklariert in Zeile 6: +@record +class C: + x: int y: int = 'foo' \ No newline at end of file diff --git a/python/file-test-data/basics/optionalAttr.err_en b/python/file-test-data/basics/optionalAttr.err_en index 21401c7..23aff00 100644 --- a/python/file-test-data/basics/optionalAttr.err_en +++ b/python/file-test-data/basics/optionalAttr.err_en @@ -10,4 +10,7 @@ But the default value has type `str`. ## File file-test-data/basics/optionalAttr.py ## Parameter declared in line 6: +@record +class C: + x: int y: int = 'foo' \ No newline at end of file diff --git a/python/file-test-data/basics/record.err b/python/file-test-data/basics/record.err index 18d0c1a..c553f14 100644 --- a/python/file-test-data/basics/record.err +++ b/python/file-test-data/basics/record.err @@ -14,4 +14,7 @@ p = Person("Alice", "30") ## Typ deklariert in Zeile 6: +@record +class Person: + name: str age: int \ No newline at end of file diff --git a/python/file-test-data/basics/record.err_en b/python/file-test-data/basics/record.err_en index bc2c2d2..602ce22 100644 --- a/python/file-test-data/basics/record.err_en +++ b/python/file-test-data/basics/record.err_en @@ -14,4 +14,7 @@ p = Person("Alice", "30") ## Type declared in line 6: +@record +class Person: + name: str age: int \ No newline at end of file diff --git a/python/file-test-data/basics/recordDecl.err b/python/file-test-data/basics/recordDecl.err new file mode 100644 index 0000000..ecac913 --- /dev/null +++ b/python/file-test-data/basics/recordDecl.err @@ -0,0 +1,21 @@ +Traceback (most recent call last): + File "file-test-data/basics/recordDecl.py", line 15, in + c.count = 'eins' + +WyppTypeError: 'eins' + +Attribut `count` des Records `Counter` deklariert als Typ `int`. +Das Attribut kann nicht auf einen Wert vom Typ `str` gesetzt werden. + +## Datei file-test-data/basics/recordDecl.py +## Fehlerhafte Zuweisung in Zeile 15: + +c.count = 'eins' + +## Typ deklariert in Zeile 7: + +@record(mutable=True) +class Counter: + # Name des Zählers + name: str + count: int diff --git a/python/file-test-data/basics/recordDecl.err_en b/python/file-test-data/basics/recordDecl.err_en new file mode 100644 index 0000000..e37bdb1 --- /dev/null +++ b/python/file-test-data/basics/recordDecl.err_en @@ -0,0 +1,21 @@ +Traceback (most recent call last): + File "file-test-data/basics/recordDecl.py", line 15, in + c.count = 'eins' + +WyppTypeError: 'eins' + +Attribute `count` of record `Counter` declared with type `int`. +Cannot set attribute to value of type `str`. + +## File file-test-data/basics/recordDecl.py +## Problematic assignment in line 15: + +c.count = 'eins' + +## Type declared in line 7: + +@record(mutable=True) +class Counter: + # Name des Zählers + name: str + count: int diff --git a/python/file-test-data/basics/recordDecl.py b/python/file-test-data/basics/recordDecl.py new file mode 100644 index 0000000..3465c6e --- /dev/null +++ b/python/file-test-data/basics/recordDecl.py @@ -0,0 +1,15 @@ +from wypp import * + +@record(mutable=True) +class Counter: + # Name des Zählers + name: str + count: int + + def inc(self) -> None: + self.count = self.count + 1 + +c = Counter('clicks', 0) +c.inc() +# The whole record up to the last attribute must be shown, with count highlighted +c.count = 'eins' diff --git a/python/file-test-data/extras/declared-at-missing.err b/python/file-test-data/extras/declared-at-missing.err index db508f9..4edcb45 100644 --- a/python/file-test-data/extras/declared-at-missing.err +++ b/python/file-test-data/extras/declared-at-missing.err @@ -13,4 +13,8 @@ semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )[0; ## Typ deklariert in Zeile 19: +@record +class Semester: + degreeProgram: str + semester: str courses: tuple[CourseM, ...] \ No newline at end of file diff --git a/python/file-test-data/extras/testForwardRef4.err b/python/file-test-data/extras/testForwardRef4.err index b644f3f..d86e726 100644 --- a/python/file-test-data/extras/testForwardRef4.err +++ b/python/file-test-data/extras/testForwardRef4.err @@ -7,4 +7,6 @@ WyppTypeError: ungültiger Typ `FooX` ## Datei file-test-data/extras/testForwardRef4.py ## Typ deklariert in Zeile 5: +@record +class Test: foo: 'FooX' diff --git a/python/file-test-data/extras/testForwardRef5.err b/python/file-test-data/extras/testForwardRef5.err index 1b164b9..578699d 100644 --- a/python/file-test-data/extras/testForwardRef5.err +++ b/python/file-test-data/extras/testForwardRef5.err @@ -13,4 +13,6 @@ garage = Garage(cars=[Car(color='red'), "Not A Car"]) ## Typ deklariert in Zeile 11: +@record +class Garage: cars: list['Car'] diff --git a/python/file-test-data/extras/testRecordSetTypeForwardRef.err b/python/file-test-data/extras/testRecordSetTypeForwardRef.err index 929f2ad..bba9e36 100644 --- a/python/file-test-data/extras/testRecordSetTypeForwardRef.err +++ b/python/file-test-data/extras/testRecordSetTypeForwardRef.err @@ -16,4 +16,6 @@ Das Attribut kann nicht auf einen Wert vom Typ `str` gesetzt werden. ## Typ deklariert in Zeile 8: +@record(mutable=True) +class Record: 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 c26ff50..d22c8ea 100644 --- a/python/file-test-data/extras/testRecordSetTypes.err +++ b/python/file-test-data/extras/testRecordSetTypes.err @@ -16,4 +16,7 @@ Das Attribut kann nicht auf einen Wert vom Typ `str` gesetzt werden. ## Typ deklariert in Zeile 5: - x : int \ No newline at end of file +@record(mutable=True) +class Record: + x : int + y : list[str] \ 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 6bd0efa..463869d 100644 --- a/python/file-test-data/extras/testRecordTypes.err +++ b/python/file-test-data/extras/testRecordTypes.err @@ -14,4 +14,7 @@ p = Point(1, '5') ## Typ deklariert in Zeile 6: +@record +class Point: + x: int y: int \ No newline at end of file diff --git a/python/file-test-data/extras/testTypesRecordInheritance.err b/python/file-test-data/extras/testTypesRecordInheritance.err index e0a40bb..2c247cf 100644 --- a/python/file-test-data/extras/testTypesRecordInheritance.err +++ b/python/file-test-data/extras/testTypesRecordInheritance.err @@ -14,4 +14,6 @@ Point3D(1,2, "foo") ## Typ deklariert in Zeile 11: +@record +class Point3D(Point2D): z: int \ No newline at end of file