These are not bugs — they are design-level observations from a code review that may be worth considering for future development. No action is required.
1. Py_TPFLAGS_IMMUTABLETYPE not set on any type
All 6 types are created with PyType_FromSpec but none set Py_TPFLAGS_IMMUTABLETYPE (available since Python 3.10, which is the minimum version). This flag marks a type as immutable, preventing monkey-patching from Python (Type.new_attr = ...). For constraint solver types, immutability is the expected semantic.
Setting it would:
- Prevent accidental type mutation from user code
- Enable interpreter-level optimizations for immutable types
- Better express the intent that these types are not designed to be modified at runtime
The tradeoff: any code that monkey-patches these types (unlikely for a constraint solver) would break.
2. Global state limits subinterpreter support despite multi-phase init
kiwisolver already uses multi-phase init (Py_mod_exec slot) — the modern pattern. However, all 12 PyObject* pointers (6 types + 6 exceptions) are stored in C globals rather than per-module state (m_size = 0). This means Py_mod_exec running in a second subinterpreter overwrites the globals from the first, creating dangling pointers when the first interpreter is destroyed.
Full subinterpreter support would require:
- Moving all 12 globals into a module state struct (
m_size = sizeof(...))
- Using
PyType_FromModuleAndSpec instead of PyType_FromSpec (to enable PyType_GetModuleState from type methods)
- Adding
m_traverse/m_clear/m_free to manage the state
This is a MEDIUM-difficulty migration because kiwisolver already has the hardest parts done (multi-phase init, heap types). The main challenge is threading module state access through the symbolics.h templates, which currently access TypeObject globals for type checking and object construction.
Whether this is worth doing depends on whether subinterpreter support is a goal for kiwisolver's users (e.g., matplotlib in subinterpreter-based parallel rendering).
3. richcompare raises TypeError instead of returning Py_NotImplemented
Variable_richcompare, Term_richcompare, and Expression_richcompare raise TypeError directly for unsupported comparison operations (<, !=, >) instead of returning Py_NotImplemented. The Python data model convention is to return Py_NotImplemented to give the other operand's type a chance to handle the comparison via reflected methods.
However, for kiwisolver's domain this is arguably the right design:
==, <=, >= create constraints, not boolean results
<, >, != have no mathematical meaning for constraint variables
- Raising
TypeError immediately gives a clear error message rather than falling through to a confusing default comparison
This is a deliberate design choice, not an oversight. Noted for documentation purposes.
These observations come from a systematic review using cext-review-toolkit. The full report covers all 10 analysis dimensions and is available on request.
These are not bugs — they are design-level observations from a code review that may be worth considering for future development. No action is required.
1.
Py_TPFLAGS_IMMUTABLETYPEnot set on any typeAll 6 types are created with
PyType_FromSpecbut none setPy_TPFLAGS_IMMUTABLETYPE(available since Python 3.10, which is the minimum version). This flag marks a type as immutable, preventing monkey-patching from Python (Type.new_attr = ...). For constraint solver types, immutability is the expected semantic.Setting it would:
The tradeoff: any code that monkey-patches these types (unlikely for a constraint solver) would break.
2. Global state limits subinterpreter support despite multi-phase init
kiwisolver already uses multi-phase init (
Py_mod_execslot) — the modern pattern. However, all 12PyObject*pointers (6 types + 6 exceptions) are stored in C globals rather than per-module state (m_size = 0). This meansPy_mod_execrunning in a second subinterpreter overwrites the globals from the first, creating dangling pointers when the first interpreter is destroyed.Full subinterpreter support would require:
m_size = sizeof(...))PyType_FromModuleAndSpecinstead ofPyType_FromSpec(to enablePyType_GetModuleStatefrom type methods)m_traverse/m_clear/m_freeto manage the stateThis is a MEDIUM-difficulty migration because kiwisolver already has the hardest parts done (multi-phase init, heap types). The main challenge is threading module state access through the
symbolics.htemplates, which currently accessTypeObjectglobals for type checking and object construction.Whether this is worth doing depends on whether subinterpreter support is a goal for kiwisolver's users (e.g., matplotlib in subinterpreter-based parallel rendering).
3. richcompare raises TypeError instead of returning Py_NotImplemented
Variable_richcompare,Term_richcompare, andExpression_richcompareraiseTypeErrordirectly for unsupported comparison operations (<,!=,>) instead of returningPy_NotImplemented. The Python data model convention is to returnPy_NotImplementedto give the other operand's type a chance to handle the comparison via reflected methods.However, for kiwisolver's domain this is arguably the right design:
==,<=,>=create constraints, not boolean results<,>,!=have no mathematical meaning for constraint variablesTypeErrorimmediately gives a clear error message rather than falling through to a confusing default comparisonThis is a deliberate design choice, not an oversight. Noted for documentation purposes.
These observations come from a systematic review using cext-review-toolkit. The full report covers all 10 analysis dimensions and is available on request.