You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] 3.3.0 regression: dict[str, str].get(missing_key) is typed as str, so the returned None takes str fast paths (bool/len/in give wrong results) #7991
Since 3.3.0, the declared item type of a dict[str, str] is used to type the result of .get(key) as str. When the key is missing, .get() returns None, but the generated code applies the str fast paths to it without a None check. Truthiness silently returns the wrong answer, and len() reads out of bounds.
Cython 3.3.0 (vs 3.2.5), CPython 3.12.3, Linux x86_64 (also observed on aarch64), gcc 15.
This hit us in production: a value read with .get() from a dict[str, str] | None passed an if v guard while being None, so None flowed into code that could not receive it.
Thanks for the report. The return type of the get method in our builtin methods table is probably declared as the dict value type, but should be marked as optional - assuming that is already supported. I don't think we needed that case yet. Worth a try though.
i confirm the bug. But I am puzzled what is purpose of the test __pyx_t_1 == Py_None in line:
if (!(likely(PyUnicode_CheckExact(__pyx_t_1))||((__pyx_t_1) ==Py_None) ||__Pyx_RaiseUnexpectedTypeError("str", __pyx_t_1))) __PYX_ERR(0, 4, __pyx_L1_error)
It should not guard against None value? And if it is not guarding what is the purpose of that expression? Update: The condition just allows None and str values... Now it is clear for me the purpouse of the test...
Also it is interesting that annotating variable instead it adds correct None guard:
Produces the same code just adds extra None guard:
if (unlikely(__pyx_v_v==Py_None)) {
PyErr_SetString(PyExc_TypeError, "object of type \047NoneType\047 has no len()");
__PYX_ERR(0, 5, __pyx_L1_error)
}
__pyx_t_2=__Pyx_PyUnicode_GET_LENGTH(__pyx_v_v); if (unlikely(__pyx_t_2== ((Py_ssize_t)-1))) __PYX_ERR(0, 5, __pyx_L1_error)
__pyx_t_1=PyLong_FromSsize_t(__pyx_t_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
Hence two points are unclear for me:
What is the purpose of the first Py_None equality test Update, now I understand that code it basically allows str or None values ...
Why plain str variable annotation adds second (correct) guard but inference to the same type (str) does not.
Activity
scoder commentedon Sep 13, 2026
matusvalo commentedon Sep 13, 2026
i confirm the bug. But I am puzzled what is purpose of the test
__pyx_t_1 == Py_Nonein line:It should not guard againstUpdate: The condition just allows None and str values... Now it is clear for me the purpouse of the test...Nonevalue? And if it is not guarding what is the purpose of that expression?Also it is interesting that annotating variable instead it adds correct
Noneguard:Produces the same code just adds extra None guard:
Hence two points are unclear for me:
What is the purpose of the firstUpdate, now I understand that code it basically allowsPy_Noneequality teststrorNonevalues ...strvariable annotation adds second (correct) guard but inference to the same type (str) does not.matusvalo commentedon Sep 13, 2026
OK the fix was more simpler than I thought. Fixing PR is linked to this issue.
matusvalo commentedon Sep 15, 2026
I updated description with one additional case.
Fix handling default values in typed dictionaries (GH-8003)