From c0e6f9d332d709130fe62b758cd0dcf8b0ad8328 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 10:49:12 +0200 Subject: [PATCH 1/6] directly export typing.Literal to avoid pylance warnings --- python/code/wypp/__init__.py | 18 +++++------------- python/code/wypp/writeYourProgram.py | 22 +++------------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/python/code/wypp/__init__.py b/python/code/wypp/__init__.py index a25b4adc..40747695 100644 --- a/python/code/wypp/__init__.py +++ b/python/code/wypp/__init__.py @@ -5,21 +5,13 @@ import typing -# Exported names that are available for star imports (in alphabetic order) -Any = w.Any -Callable = w.Callable -Generator = w.Generator -Iterable = w.Iterable -Iterator = w.Iterator -Literal = w.Literal -Mapping = w.Mapping -Optional = w.Optional -Sequence = w.Sequence -Protocol = w.Protocol -Union = w.Union +# Exported names that are available for star imports (mostly in alphabetic order) +from typing import Any, Callable, Generator, Iterable, Iterator, Literal, Mapping, Optional, \ + Protocol, Sequence, Union +from dataclasses import dataclass + check = w.check checkFail = w.checkFail -dataclass = w.dataclass floatNegative = w.floatNegative floatNonNegative = w.floatNonNegative floatNonPositive = w.floatNonPositive diff --git a/python/code/wypp/writeYourProgram.py b/python/code/wypp/writeYourProgram.py index a04442db..197f269d 100644 --- a/python/code/wypp/writeYourProgram.py +++ b/python/code/wypp/writeYourProgram.py @@ -1,4 +1,5 @@ import typing +from typing import Any import dataclasses import inspect import errors @@ -16,23 +17,6 @@ def _debug(s): if _DEBUG: print('[DEBUG] ' + s) -# Types -Any = typing.Any -Optional = typing.Optional -Union = typing.Union -Literal = typing.Literal -Iterable = typing.Iterable -Iterator = typing.Iterator -Sequence = typing.Sequence -Generator = typing.Generator -ForwardRef = typing.ForwardRef -Protocol = typing.Protocol - -Mapping = typing.Mapping - -Callable = typing.Callable - -dataclass = dataclasses.dataclass record = records.record intPositive = typing.Annotated[int, lambda i: i > 0, 'intPositive'] @@ -46,7 +30,7 @@ def _debug(s): floatNegative = typing.Annotated[float, lambda x: x < 0, 'floatNegative'] floatNonPositive = typing.Annotated[float, lambda x: x <= 0, 'floatNonPositive'] -class Lock(Protocol): +class Lock(typing.Protocol): def acquire(self, blocking: bool = True, timeout:int = -1) -> Any: pass def release(self) -> Any: @@ -54,7 +38,7 @@ def release(self) -> Any: def locked(self) -> Any: pass -LockFactory = typing.Annotated[Callable[[], Lock], 'LockFactory'] +LockFactory = typing.Annotated[typing.Callable[[], Lock], 'LockFactory'] T = typing.TypeVar('T') U = typing.TypeVar('U') From 888a61a605be152b17422713d82ea96f52c3ab95 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 10:49:43 +0200 Subject: [PATCH 2/6] do not overwrite extraPaths, just add our path --- src/extension.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 6453793c..14963754 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -283,8 +283,9 @@ async function fixPylanceConfig( // extraPaths const keyExtraPaths = 'analysis.extraPaths'; const extra = cfg.get(keyExtraPaths) ?? []; - if (extra.length !== 1 || extra[0] !== libDir) { - await tryUpdate(keyExtraPaths, [libDir]); + if (!extra.includes(libDir)) { + const newExtra = [...extra, libDir]; + await tryUpdate(keyExtraPaths, newExtra); } // typechecking off From 2a6e9767ea9afe7dab3c62d926ac34266b33943e Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 10:50:23 +0200 Subject: [PATCH 3/6] add missing files --- python/file-test-data/basics/13.10.VL_ok.err | 0 python/file-test-data/basics/13.10.VL_ok.out | 1 + python/file-test-data/basics/13.10.VL_ok.py | 3 +++ python/file-test-data/basics/x.y_ok.err | 0 python/file-test-data/basics/x.y_ok.out | 1 + python/file-test-data/basics/x.y_ok.py | 3 +++ 6 files changed, 8 insertions(+) create mode 100644 python/file-test-data/basics/13.10.VL_ok.err create mode 100644 python/file-test-data/basics/13.10.VL_ok.out create mode 100644 python/file-test-data/basics/13.10.VL_ok.py create mode 100644 python/file-test-data/basics/x.y_ok.err create mode 100644 python/file-test-data/basics/x.y_ok.out create mode 100644 python/file-test-data/basics/x.y_ok.py diff --git a/python/file-test-data/basics/13.10.VL_ok.err b/python/file-test-data/basics/13.10.VL_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/file-test-data/basics/13.10.VL_ok.out b/python/file-test-data/basics/13.10.VL_ok.out new file mode 100644 index 00000000..e965047a --- /dev/null +++ b/python/file-test-data/basics/13.10.VL_ok.out @@ -0,0 +1 @@ +Hello diff --git a/python/file-test-data/basics/13.10.VL_ok.py b/python/file-test-data/basics/13.10.VL_ok.py new file mode 100644 index 00000000..f163aee2 --- /dev/null +++ b/python/file-test-data/basics/13.10.VL_ok.py @@ -0,0 +1,3 @@ +from wypp import * + +print('Hello') diff --git a/python/file-test-data/basics/x.y_ok.err b/python/file-test-data/basics/x.y_ok.err new file mode 100644 index 00000000..e69de29b diff --git a/python/file-test-data/basics/x.y_ok.out b/python/file-test-data/basics/x.y_ok.out new file mode 100644 index 00000000..e965047a --- /dev/null +++ b/python/file-test-data/basics/x.y_ok.out @@ -0,0 +1 @@ +Hello diff --git a/python/file-test-data/basics/x.y_ok.py b/python/file-test-data/basics/x.y_ok.py new file mode 100644 index 00000000..f163aee2 --- /dev/null +++ b/python/file-test-data/basics/x.y_ok.py @@ -0,0 +1,3 @@ +from wypp import * + +print('Hello') From 5dbd5afa6cb1901fafa08678e14872c4bdf4bb67 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 13:24:11 +0200 Subject: [PATCH 4/6] fix highlighting with umlauts --- python/code/wypp/location.py | 71 +++++++++++++++++-- python/file-test-data/basics/umlaute.err | 17 +++++ python/file-test-data/basics/umlaute.out | 0 python/file-test-data/basics/umlaute.py | 6 ++ .../file-test-data/extras/testImpossible.err | 2 +- python/file-test-data/extras/testTodo.err | 2 +- 6 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 python/file-test-data/basics/umlaute.err create mode 100644 python/file-test-data/basics/umlaute.out create mode 100644 python/file-test-data/basics/umlaute.py diff --git a/python/code/wypp/location.py b/python/code/wypp/location.py index 12de0a0e..f9f357f6 100644 --- a/python/code/wypp/location.py +++ b/python/code/wypp/location.py @@ -13,6 +13,63 @@ import parsecache from parsecache import FunMatcher import paths +import tokenize +import os + +@dataclass +class EncodedBytes: + bytes: bytes + encoding: str + def __len__(self): + return len(self.bytes) + def countLeadingSpaces(self) -> int: + return len(self.bytes) - len(self.bytes.lstrip()) + def decoded(self) -> str: + return self.bytes.decode(self.encoding, errors='replace') + @overload + def __getitem__(self, key: int) -> int: ... + @overload + def __getitem__(self, key: slice) -> str: ... + def __getitem__(self, key: int | slice) -> int | str: + if isinstance(key, int): + return self.bytes[key] + else: + b = self.bytes[key] + return b.decode(self.encoding, errors='replace') + +@dataclass +class EncodedByteLines: + bytes: list[bytes] + encoding: str + +_cache: dict[str, EncodedByteLines] = {} +def getline(filename, lineno): + """ + Returns a line of some source file as a bytearray. We use byte arrays because + location offsets are byte offsets. + """ + p = os.path.normpath(os.path.abspath(filename)) + if p in _cache: + lines = _cache[p] + else: + with open(filename, 'rb') as f: + byteLines = f.readlines() + i = 0 + def nextLine() -> bytes: + nonlocal i + if i < len(byteLines): + x = byteLines[i] + i = i + 1 + return x + else: + return b'' + encoding, _ = tokenize.detect_encoding(nextLine) + lines = EncodedByteLines(byteLines, encoding) + if 1 <= lineno <= len(lines.bytes): + x = lines.bytes[lineno - 1].rstrip(b'\n') + else: + x = b'' + return EncodedBytes(x, encoding) @dataclass class Loc: @@ -38,7 +95,7 @@ def code(self) -> Optional[str]: case (startLine, startCol, endLine, endCol): result = [] for lineNo in range(startLine, startLine+1): - line = linecache.getline(self.filename, lineNo).rstrip("\n") + line = getline(self.filename, lineNo) c1 = startCol if lineNo == startLine else 0 c2 = endCol if lineNo == endLine else len(line) result.append(line[c1:c2]) @@ -84,27 +141,27 @@ def highlight(s: str, mode: HighlightMode) -> str: @dataclass class SourceLine: - line: str # without trailing \n + line: EncodedBytes # without trailing \n span: Optional[tuple[int, int]] # (inclusive, exclusive) - def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv'): + def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv') -> str: 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 + return self.line.decoded() def highlightedLines(loc: Loc) -> list[SourceLine]: match loc.fullSpan(): case None: - line = linecache.getline(loc.filename, loc.startLine).rstrip("\n") + line = getline(loc.filename, loc.startLine) 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()) + line = getline(loc.filename, lineNo) + leadingSpaces = line.countLeadingSpaces() c1 = startCol if lineNo == startLine else leadingSpaces c2 = endCol if lineNo == endLine else len(line) result.append(SourceLine(line, (c1, c2))) diff --git a/python/file-test-data/basics/umlaute.err b/python/file-test-data/basics/umlaute.err new file mode 100644 index 00000000..7f1f3fd6 --- /dev/null +++ b/python/file-test-data/basics/umlaute.err @@ -0,0 +1,17 @@ +Traceback (most recent call last): + File "file-test-data/basics/umlaute.py", line 6, in + check(äüßö("1"), 1) + +WyppTypeError: "1" + +Der Aufruf der Funktion `äüßö` erwartet einen Wert vom Typ `int` als erstes Argument. +Aber der übergebene Wert hat den Typ `str`. + +## Datei file-test-data/basics/umlaute.py +## Fehlerhafter Aufruf in Zeile 6: + +check(äüßö("1"), 1) + +## Typ deklariert in Zeile 3: + +def äüßö(x: int) -> int: diff --git a/python/file-test-data/basics/umlaute.out b/python/file-test-data/basics/umlaute.out new file mode 100644 index 00000000..e69de29b diff --git a/python/file-test-data/basics/umlaute.py b/python/file-test-data/basics/umlaute.py new file mode 100644 index 00000000..e686b306 --- /dev/null +++ b/python/file-test-data/basics/umlaute.py @@ -0,0 +1,6 @@ +from wypp import * + +def äüßö(x: int) -> int: + return x + 1 + +check(äüßö("1"), 1) diff --git a/python/file-test-data/extras/testImpossible.err b/python/file-test-data/extras/testImpossible.err index 95961a19..c35e73bb 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 334, in impossible + File "code/wypp/writeYourProgram.py", line 318, in impossible raise errors.ImpossibleError(msg) Das Unmögliche ist passiert! diff --git a/python/file-test-data/extras/testTodo.err b/python/file-test-data/extras/testTodo.err index d4bcb7f7..459d5648 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 328, in todo + File "code/wypp/writeYourProgram.py", line 312, in todo raise errors.TodoError(msg) TODO From 219c7c54961e8eefd774d9e48597e3ffa632a3f3 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 13:24:26 +0200 Subject: [PATCH 5/6] version bump and changelog for 2.0.8 --- ChangeLog.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9c4466e5..268caa03 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,8 +1,11 @@ # Write Your Python Program - CHANGELOG +* 2.0.8 (2025-10-16) + * Fix vscode warning for literals + * Fix highlighting for files with umlauts * 2.0.7 (2025-10-14) * Fix #184 (filenames with dots) - * Support python 3.13 + * Support python 3.14 * 2.0.6 (2025-10-08) * Settings for language #180 * Only warn of settings cannot be saved #182 diff --git a/package.json b/package.json index bb6fe6f9..31c7707b 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.7", + "version": "2.0.8", "publisher": "StefanWehr", "icon": "icon.png", "engines": { From aedd5638d62988f7aea767a656611149ba5743e7 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 16 Oct 2025 13:30:21 +0200 Subject: [PATCH 6/6] bug fixes --- python/code/wypp/drawingLib.py | 8 +++++--- python/code/wypp/location.py | 7 +++++-- python/tests/test_misc.py | 1 + python/tests/test_record.py | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/python/code/wypp/drawingLib.py b/python/code/wypp/drawingLib.py index 8b55560c..edc940b0 100644 --- a/python/code/wypp/drawingLib.py +++ b/python/code/wypp/drawingLib.py @@ -1,6 +1,8 @@ import time import threading import writeYourProgram as _w +from typing import Literal, Sequence + # Do not import tkinter at the top-level. Someone with no installation of tkinter should # be able to user WYPP without drawing support. @@ -14,9 +16,9 @@ class Point: x: float y: float -ShapeKind = _w.Literal['ellipsis', 'rectangle'] +type ShapeKind = Literal['ellipsis', 'rectangle'] -Color = _w.Literal['red', 'green', 'blue', 'yellow', 'black', 'white'] +type Color = Literal['red', 'green', 'blue', 'yellow', 'black', 'white'] @_w.record class FixedShape: @@ -58,7 +60,7 @@ def _drawCoordinateSystem(canvas, windowSize: Size): canvas.create_line(x, 0, x, windowSize.height, dash=(4,2)) canvas.create_line(0, y, windowSize.width, y, dash=(4,2)) -def drawFixedShapes(shapes: _w.Sequence[FixedShape], +def drawFixedShapes(shapes: Sequence[FixedShape], withCoordinateSystem=False, stopAfter=None) -> None: try: diff --git a/python/code/wypp/location.py b/python/code/wypp/location.py index f9f357f6..f3722011 100644 --- a/python/code/wypp/location.py +++ b/python/code/wypp/location.py @@ -52,8 +52,11 @@ def getline(filename, lineno): if p in _cache: lines = _cache[p] else: - with open(filename, 'rb') as f: - byteLines = f.readlines() + try: + with open(filename, 'rb') as f: + byteLines = f.readlines() + except Exception: + byteLines = [] i = 0 def nextLine() -> bytes: nonlocal i diff --git a/python/tests/test_misc.py b/python/tests/test_misc.py index bb4ee77f..9334883f 100644 --- a/python/tests/test_misc.py +++ b/python/tests/test_misc.py @@ -1,5 +1,6 @@ import unittest from writeYourProgram import * +from typing import * setDieOnCheckFailures(True) diff --git a/python/tests/test_record.py b/python/tests/test_record.py index d0113e9c..e9fc2229 100644 --- a/python/tests/test_record.py +++ b/python/tests/test_record.py @@ -4,6 +4,7 @@ import traceback import dataclasses import stacktrace +from typing import Literal initModule()