From acc4f4104bb9e6a5ad5f34a4f9c8e2f354cec20c Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Sat, 25 Jul 2026 15:52:02 +0800 Subject: [PATCH 1/5] add `__subclasses__` --- src/bindings/py_object.c | 15 +++++++++++++++ tests/500_reflection.py | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/bindings/py_object.c b/src/bindings/py_object.c index 26dc21c8..19995da8 100644 --- a/src/bindings/py_object.c +++ b/src/bindings/py_object.c @@ -124,6 +124,20 @@ static bool type__annotations__(int argc, py_Ref argv) { return true; } +static bool type__subclasses__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + py_TypeInfo* base_ti = py_touserdata(argv); + py_newlist(py_retval()); + + for(py_Type i = 1; i < pk_current_vm->types.length; i++) { + py_TypeInfo* ti = pk_typeinfo(i); + if(ti->base == base_ti->index) { + py_list_append(py_retval(), &ti->self); + } + } + return true; +} + void pk_object__register() { py_bindmagic(tp_object, __new__, pk__object_new); @@ -142,4 +156,5 @@ void pk_object__register() { py_bindproperty(tp_type, "__name__", type__name__, NULL); py_bindproperty(tp_object, "__dict__", object__dict__, NULL); py_bindproperty(tp_type, "__annotations__", type__annotations__, NULL); + py_bindmethod(tp_type, "__subclasses__", type__subclasses__); } \ No newline at end of file diff --git a/tests/500_reflection.py b/tests/500_reflection.py index c4118174..0e7b8ad1 100644 --- a/tests/500_reflection.py +++ b/tests/500_reflection.py @@ -50,3 +50,21 @@ assert hasattr(a, 'zzz') assert not hasattr(a, '') +class Base: + pass + +class Child1(Base): + pass + +class Child2(Base): + pass + +class GrandChild(Child1): + pass + +subs = Base.__subclasses__() +assert type(subs) is list +assert Child1 in subs +assert Child2 in subs +assert GrandChild not in subs + From a06e6ac6ba68214171e995a7cc1ffe205a070d8b Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Mon, 27 Jul 2026 03:18:48 +0800 Subject: [PATCH 2/5] fix bugs --- src/compiler/lexer.c | 13 ++++-- src/interpreter/vm.c | 97 ++++++++++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 34a8343d..bab4c4de 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -189,8 +189,8 @@ static Error* LexerError(Lexer* self, const char* fmt, ...) { err->src = self->src; PK_INCREF(self->src); err->lineno = self->current_line; - const char* end = self->src->source->data + self->src->source->size; - if(self->curr_char <= end && *self->curr_char == '\n') { err->lineno--; } + const char* p_end = self->src->source->data + self->src->source->size; + if(self->curr_char <= p_end && *self->curr_char == '\n') { err->lineno--; } va_list args; va_start(args, fmt); vsnprintf(err->msg, sizeof(err->msg), fmt, args); @@ -282,9 +282,16 @@ static Error* _eat_string(Lexer* self, c11_sbuf* buff, char quote, enum StringTy case 'b': c11_sbuf__write_char(buff, '\b'); break; case 'f': c11_sbuf__write_char(buff, '\f'); break; case 'v': c11_sbuf__write_char(buff, '\v'); break; - // Special case for the often used \0 while we don't have full support for octal literals. + // Special case for the often used \0 while we don't have full support for octal + // literals. case '0': c11_sbuf__write_char(buff, '\0'); break; case 'x': { + // check there are at least 2 chars can read + const char* p_end = self->src->source->data + self->src->source->size; + if(p_end - self->curr_char < 2) { + return LexerError(self, "invalid hex escape"); + } + char hex[3] = {eatchar(self), eatchar(self), '\0'}; int code; if(sscanf(hex, "%x", &code) != 1 || code > 0xFF) { diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 1db9ef6d..47578ded 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -713,53 +713,62 @@ void ManagedHeap__mark(ManagedHeap* self) { } } - void* ud = PyObject__userdata(obj); - switch(obj->type) { - case tp_list: { - List* self = ud; - for(int i = 0; i < self->length; i++) { - py_TValue* val = c11__at(py_TValue, self, i); - pk__mark_value(val); + if(obj->type > tp_object) { + // NOTE: `defaultdict` -> `dict` -> `object` + // NOTE: native types must extend from `object`. + py_TypeInfo* ti = pk_typeinfo(obj->type); + while(ti->base != tp_object) { + ti = ti->base_ti; + } + + void* ud = PyObject__userdata(obj); + switch(ti->index) { + case tp_list: { + List* self = ud; + for(int i = 0; i < self->length; i++) { + py_TValue* val = c11__at(py_TValue, self, i); + pk__mark_value(val); + } + break; } - break; - } - case tp_dict: { - Dict* self = ud; - for(int i = 0; i < self->entries.length; i++) { - DictEntry* entry = c11__at(DictEntry, &self->entries, i); - if(py_isnil(&entry->key)) continue; - pk__mark_value(&entry->key); - pk__mark_value(&entry->val); + case tp_dict: { + Dict* self = ud; + for(int i = 0; i < self->entries.length; i++) { + DictEntry* entry = c11__at(DictEntry, &self->entries, i); + if(py_isnil(&entry->key)) continue; + pk__mark_value(&entry->key); + pk__mark_value(&entry->val); + } + break; } - break; - } - case tp_generator: { - Generator* self = ud; - if(self->frame) Frame__gc_mark(self->frame, p_stack); - break; - } - case tp_function: { - function__gc_mark(ud, p_stack); - break; - } - case tp_BaseException: { - BaseException* self = ud; - pk__mark_value(&self->args); - pk__mark_value(&self->inner_exc); - c11__foreach(BaseExceptionFrame, &self->stacktrace, frame) { - pk__mark_value(&frame->locals); - pk__mark_value(&frame->globals); + case tp_generator: { + Generator* self = ud; + if(self->frame) Frame__gc_mark(self->frame, p_stack); + break; + } + case tp_function: { + function__gc_mark(ud, p_stack); + break; + } + case tp_BaseException: { + BaseException* self = ud; + pk__mark_value(&self->args); + pk__mark_value(&self->inner_exc); + c11__foreach(BaseExceptionFrame, &self->stacktrace, frame) { + pk__mark_value(&frame->locals); + pk__mark_value(&frame->globals); + } + break; + } + case tp_code: { + CodeObject* self = ud; + CodeObject__gc_mark(self, p_stack); + break; + } + case tp_chunked_array2d: { + c11_chunked_array2d__mark(ud, p_stack); + break; } - break; - } - case tp_code: { - CodeObject* self = ud; - CodeObject__gc_mark(self, p_stack); - break; - } - case tp_chunked_array2d: { - c11_chunked_array2d__mark(ud, p_stack); - break; } } } From 799829a8aa9f09664744d1554ede4a3142d1f9cd Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 28 Jul 2026 18:42:34 +0800 Subject: [PATCH 3/5] fix PK_THREAD_LOCAL --- include/pocketpy/objects/base.h | 2 +- src/public/GlobalSetup.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pocketpy/objects/base.h b/include/pocketpy/objects/base.h index 22e7f051..be3714c4 100644 --- a/include/pocketpy/objects/base.h +++ b/include/pocketpy/objects/base.h @@ -4,7 +4,7 @@ typedef struct PyObject PyObject; typedef struct VM VM; -extern _Thread_local VM* pk_current_vm; +extern PK_THREAD_LOCAL VM* pk_current_vm; typedef struct py_TValue { py_Type type; diff --git a/src/public/GlobalSetup.c b/src/public/GlobalSetup.c index d4d044fc..5c749544 100644 --- a/src/public/GlobalSetup.c +++ b/src/public/GlobalSetup.c @@ -5,7 +5,7 @@ #include "pocketpy/common/name.h" #include "pocketpy/interpreter/vm.h" -_Thread_local VM* pk_current_vm; +PK_THREAD_LOCAL VM* pk_current_vm; static bool pk_initialized; static bool pk_finalized; From fed2bcea78b18cc8c3375bcc5c0cd734eb94496a Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Tue, 28 Jul 2026 18:55:16 +0800 Subject: [PATCH 4/5] update to 19 --- .github/workflows/main.yml | 4 ++-- run_tests.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ced216a7..c88b0172 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,11 +56,11 @@ jobs: - name: Setup Clang uses: egor-tensin/setup-clang@v1 with: - version: 17 + version: 19 platform: x64 - name: Run Sanitizers run: | - sudo apt-get install -y libclang-rt-17-dev + sudo apt-get install -y libclang-rt-19-dev bash build_g.sh bash run_tests.sh rm -rf ./main diff --git a/run_tests.sh b/run_tests.sh index 6b2c46ef..16c2c520 100644 --- a/run_tests.sh +++ b/run_tests.sh @@ -17,7 +17,7 @@ rm -rf .coverage mkdir .coverage UNITS=$(find ./ -name "*.gcno") -llvm-cov-17 gcov ${UNITS} -r -s include/ -r -s src/ >> .coverage/coverage.txt +llvm-cov-19 gcov ${UNITS} -r -s include/ -r -s src/ >> .coverage/coverage.txt mv *.gcov .coverage rm *.gcda From 71f06c4ff1c80e015c3da7b97da439815ca0c80f Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Wed, 29 Jul 2026 19:16:24 +0800 Subject: [PATCH 5/5] fix a bug of `__init__` --- src/interpreter/vm.c | 19 +++++++++++-------- tests/410_class_ex.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index 47578ded..dd4d7acb 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -579,8 +579,9 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall } if(p0->type == tp_type) { + py_Type p0_type = py_totype(p0); // [cls, NULL, args..., kwargs...] - py_Ref new_f = py_tpfindmagic(py_totype(p0), __new__); + py_Ref new_f = py_tpfindmagic(p0_type, __new__); assert(new_f && py_isnil(p0 + 1)); bool is_default_new = new_f->type == tp_nativefunc && new_f->_cfunc == pk__object_new; @@ -598,14 +599,16 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall // NOTE: previously we use `get_unbound_method` but here we just use `tpfindmagic` // >> [cls, NULL, args..., kwargs...] // >> py_retval() is the new instance - py_Ref init_f = py_tpfindmagic(py_totype(p0), __init__); + py_Ref init_f = py_tpfindmagic(p0_type, __init__); if(init_f) { - // do an inplace patch - *p0 = *init_f; // __init__ - p0[1] = self->last_retval; // self - // [__init__, self, args..., kwargs...] - if(VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR; - *py_retval() = p0[1]; // restore the new instance + if(py_isinstance(py_retval(), p0_type)) { + // do an inplace patch + *p0 = *init_f; // __init__ + p0[1] = self->last_retval; // self + // [__init__, self, args..., kwargs...] + if(VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR; + *py_retval() = p0[1]; // restore the new instance + } } else { if(is_default_new) { if(argc != 0 || kwargc != 0) { diff --git a/tests/410_class_ex.py b/tests/410_class_ex.py index e52d811d..13d4b643 100644 --- a/tests/410_class_ex.py +++ b/tests/410_class_ex.py @@ -167,4 +167,14 @@ class DerivedClass(BaseClass): return super().f() -assert DerivedClass.f() == 'BaseClass' \ No newline at end of file +assert DerivedClass.f() == 'BaseClass' + +# bad __init__ +class A: + def __new__(cls, *args, **kwargs): + return 1 + + def __init__(self): + assert False + +A()