Conversation
dd6a77c to
469ce9e
Compare
9d8efcd to
e676ca4
Compare
8ce6f94 to
295e983
Compare
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
xFrednet
left a comment
There was a problem hiding this comment.
My comments can be quite nit-picky, so please don't hesitate to push back on them :)
I'm not done reviewing everything yet, but from what I've seen thus far: Excellent job! It's amazing that you managed to get all of this together, well done :D
This review is still incomplete. From all I've read, excellent job!
xFrednet
left a comment
There was a problem hiding this comment.
Very nicely done! This is mainly a collection of questions, which probably come from me missing some context.
e269f3f to
1cdd289
Compare
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Python/immutability.c
Outdated
| }else if(PyDict_Contains(module_dict, name)){ | ||
| PyObject* value = PyDict_GetItem(module_dict, name); // value.rc = x | ||
|
|
||
| _PyDict_SetKeyImmutable((PyDictObject*)module_dict, name); | ||
|
|
||
| if(!_Py_IsImmutable(value)){ | ||
| if(PyList_Append(frontier, value)){ | ||
| goto nomemory; | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't understand what this is doing? Could you explain an example that this is catching that is a problem?
Python/immutability.c
Outdated
| }else if(PyDict_Contains(module_dict, name)){ | ||
| PyObject* value = PyDict_GetItem(module_dict, name); | ||
|
|
||
| _PyDict_SetKeyImmutable((PyDictObject*)module_dict, name); | ||
|
|
||
| if(!_Py_IsImmutable(value)){ | ||
| if(push(frontier, value)){ | ||
| goto nomemory; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I couldn't work out how to exercise this code. It doesn't appear to be hit by test_freeze. Should we be able to get coverage for this? I had an experiment with the REPL and a few modules, but this code path didn't get exercised.
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
| struct _Py_async_gen_state async_gen; | ||
| struct _Py_context_state context; | ||
| struct _Py_exc_state exc_state; | ||
| struct _Py_immutability_state immutability; |
There was a problem hiding this comment.
Does this mean that different interpreters have different views on what is freezable?
| return; | ||
| } | ||
| ob->ob_refcnt = refcnt; | ||
| ob->ob_refcnt = (ob->ob_refcnt & _Py_IMMUTABLE_MASK) | (refcnt & _Py_REFCNT_MASK); |
There was a problem hiding this comment.
Shouldn't the immutability flag always be 0 here due to the if statement above?
|
@matajoh Do we want to change the title of the PR to be ready for outside viewers? Maybe something like "Adding Deep Immutability to 3.12" |
| if (!Py_CHECKWRITE(op)) { \ | ||
| PyErr_SetObject( \ | ||
| PyExc_TypeError, \ | ||
| (PyObject *)((op))); \ | ||
| return -1; \ | ||
| } |
There was a problem hiding this comment.
Why does this create an Error object and doesn't use PyErr_WriteToImmutable like the additions in the other files?
Also why is it called _VALIDATE_WRITABLE here and _CHECK_IS_WRITABLE above? The difference seems to be the return value, but they seem to be used in the same way.
| if (type == NULL) { \ | ||
| goto error; \ | ||
| } \ | ||
| #define CREATE_TYPE(module, type, spec, base) \ |
There was a problem hiding this comment.
Is this new base parameter still needed? It seems to be always NULL in the uses below
| if (_PyImmutability_RegisterFreezable((PyTypeObject *)state->PyStructType) < 0){ | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
The file Modules/_elementtree.c uses _PyImport_GetModuleAttrString("immutable", "register_freezable"); to get the register_freezable function or handle failure if the "immutable" module is not available. This one uses the builtin function _PyImmutability_RegisterFreezable from pycore. How is it decided which function is used?
| if(self->codec->encinit != NULL){ | ||
| if(!Py_CHECKWRITE(self)){ | ||
| return PyErr_WriteToImmutable(self); | ||
| } | ||
| } |
There was a problem hiding this comment.
This file has new Py_CHECKWRITE but doesn't register the type as freezable. Is this declared somewhere else?
| #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) | ||
| # include "pycore_gc.h" // PyGC_Head | ||
| # include "pycore_runtime.h" // _Py_ID() | ||
| #endif |
There was a problem hiding this comment.
These two includes don't seem to be used in the header file, can we move them into the .c file instead?
| #include "clinic/immutablemodule.c.h" | ||
|
|
||
| typedef struct { | ||
| PyObject *not_freezable_error_obj; |
There was a problem hiding this comment.
Is this object still used? Looking at the code, it seems to only be initialized and cleared, but never used outsite of this
| static PyObject * | ||
| immutable_error(void) | ||
| { | ||
| PyThreadState *tstate = _PyThreadState_GET(); | ||
| if (!_PyErr_Occurred(tstate)) { | ||
| _PyErr_SetString(tstate, PyExc_TypeError, | ||
| "cannot modify immutable instance"); | ||
| } | ||
| return NULL; | ||
| } |
There was a problem hiding this comment.
Why does this used a custom immutable_error function and not PyErr_WriteToImmutable(). The difference seems to be that this function doesn't take the object that caused the error. But looking at the Py_CHECKWRITE(o) we always seem to have an object
| if(PyDict_SetItemString(PyModule_GetDict(frozen_importlib), "_freezable_types", state->freezable_types)){ | ||
| Py_DECREF(frozen_importlib); | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
My understanding is, that this exposes the freezable_types set via the frozen_importlib module. Is there a reason for this?
| size = PySequence_Fast_GET_SIZE(f_code->co_consts); | ||
| for(Py_ssize_t i = 0; i < size; i++){ | ||
| PyObject* value = PySequence_Fast_GET_ITEM(f_code->co_consts, i); | ||
| if(check_globals && PyUnicode_Check(value)){ |
There was a problem hiding this comment.
Couldn't we move the check_globals check outside the for loop?
| if(_Py_IsImmutable(item)){ | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Can't we move this check above check_freezable? It seems like it's way cheaper.
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
|
I've been playing with the current implementation. Seems that it’s sometimes trying to freeze type variables that are arguments to generic functions. And that can’t be done. A simple decorator example: from __future__ import annotations
from immutable import freeze
from typing import Callable
def foo[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
def fn_wrap(*args: P.args, **kwargs: P.kwargs) -> R:
return fn(*args, **kwargs)
return fn_wrap
freeze(foo) # TypeError: Cannot freeze instance of type typing.ParamSpec due to custom functionality implemented in CI’m surprised that |
Thanks for raising this. I'll investigate what is happening, and get back to you. |
This draft PR is to give a chance to view the diff between 3.12 and just adding the deep immutability from veronapy. The key difference is the use of the lowest bit of
ob_refcntto indicate immutability (so as to not impact object header size).📚 Documentation preview 📚: https://cpython-previews--51.org.readthedocs.build/