aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Python/suggestions.c
diff options
context:
space:
mode:
authorshadchin <shadchin@yandex-team.com>2024-02-12 07:53:52 +0300
committershadchin <shadchin@yandex-team.com>2024-02-12 08:07:36 +0300
commitce1b7ca3171f9158180640c6a02a74b4afffedea (patch)
treee47c1e8391b1b0128262c1e9b1e6ed4c8fff2348 /contrib/tools/python3/src/Python/suggestions.c
parent57350d96f030db90f220ce50ee591d5c5d403df7 (diff)
downloadydb-ce1b7ca3171f9158180640c6a02a74b4afffedea.tar.gz
Update Python from 3.11.8 to 3.12.2
Diffstat (limited to 'contrib/tools/python3/src/Python/suggestions.c')
-rw-r--r--contrib/tools/python3/src/Python/suggestions.c197
1 files changed, 164 insertions, 33 deletions
diff --git a/contrib/tools/python3/src/Python/suggestions.c b/contrib/tools/python3/src/Python/suggestions.c
index 5a4cf93246..ad58393490 100644
--- a/contrib/tools/python3/src/Python/suggestions.c
+++ b/contrib/tools/python3/src/Python/suggestions.c
@@ -1,8 +1,11 @@
#include "Python.h"
#include "pycore_frame.h"
+#include "pycore_runtime.h" // _PyRuntime
+#include "pycore_global_objects.h" // _Py_ID()
#include "pycore_pyerrors.h"
#include "pycore_code.h" // _PyCode_GetVarnames()
+#include "stdlib_module_names.h" // _Py_stdlib_module_names
#define MAX_CANDIDATE_ITEMS 750
#define MAX_STRING_SIZE 40
@@ -38,10 +41,8 @@ substitution_cost(char a, char b)
static Py_ssize_t
levenshtein_distance(const char *a, size_t a_size,
const char *b, size_t b_size,
- size_t max_cost)
+ size_t max_cost, size_t *buffer)
{
- static size_t buffer[MAX_STRING_SIZE];
-
// Both strings are the same (by identity)
if (a == b) {
return 0;
@@ -144,7 +145,10 @@ calculate_suggestions(PyObject *dir,
if (name_str == NULL) {
return NULL;
}
-
+ size_t *buffer = PyMem_New(size_t, MAX_STRING_SIZE);
+ if (buffer == NULL) {
+ return PyErr_NoMemory();
+ }
for (int i = 0; i < dir_size; ++i) {
PyObject *item = PyList_GET_ITEM(dir, i);
if (_PyUnicode_Equal(name, item)) {
@@ -153,6 +157,7 @@ calculate_suggestions(PyObject *dir,
Py_ssize_t item_size;
const char *item_str = PyUnicode_AsUTF8AndSize(item, &item_size);
if (item_str == NULL) {
+ PyMem_Free(buffer);
return NULL;
}
// No more than 1/3 of the involved characters should need changed.
@@ -160,8 +165,8 @@ calculate_suggestions(PyObject *dir,
// Don't take matches we've already beaten.
max_distance = Py_MIN(max_distance, suggestion_distance - 1);
Py_ssize_t current_distance =
- levenshtein_distance(name_str, name_size,
- item_str, item_size, max_distance);
+ levenshtein_distance(name_str, name_size, item_str,
+ item_size, max_distance, buffer);
if (current_distance > max_distance) {
continue;
}
@@ -170,12 +175,12 @@ calculate_suggestions(PyObject *dir,
suggestion_distance = current_distance;
}
}
- Py_XINCREF(suggestion);
- return suggestion;
+ PyMem_Free(buffer);
+ return Py_XNewRef(suggestion);
}
static PyObject *
-offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc)
+get_suggestions_for_attribute_error(PyAttributeErrorObject *exc)
{
PyObject *name = exc->name; // borrowed reference
PyObject *obj = exc->obj; // borrowed reference
@@ -195,35 +200,25 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc)
return suggestions;
}
-
static PyObject *
-offer_suggestions_for_name_error(PyNameErrorObject *exc)
+offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc)
{
- PyObject *name = exc->name; // borrowed reference
- PyTracebackObject *traceback = (PyTracebackObject *) exc->traceback; // borrowed reference
- // Abort if we don't have a variable name or we have an invalid one
- // or if we don't have a traceback to work with
- if (name == NULL || !PyUnicode_CheckExact(name) ||
- traceback == NULL || !Py_IS_TYPE(traceback, &PyTraceBack_Type)
- ) {
+ PyObject* suggestion = get_suggestions_for_attribute_error(exc);
+ if (suggestion == NULL) {
return NULL;
}
+ // Add a trailer ". Did you mean: (...)?"
+ PyObject* result = PyUnicode_FromFormat(". Did you mean: %R?", suggestion);
+ Py_DECREF(suggestion);
+ return result;
+}
- // Move to the traceback of the exception
- while (1) {
- PyTracebackObject *next = traceback->tb_next;
- if (next == NULL || !Py_IS_TYPE(next, &PyTraceBack_Type)) {
- break;
- }
- else {
- traceback = next;
- }
- }
-
- PyFrameObject *frame = traceback->tb_frame;
- assert(frame != NULL);
+static PyObject *
+get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame)
+{
PyCodeObject *code = PyFrame_GetCode(frame);
assert(code != NULL && code->co_localsplusnames != NULL);
+
PyObject *varnames = _PyCode_GetVarnames(code);
Py_DECREF(code);
if (varnames == NULL) {
@@ -235,9 +230,38 @@ offer_suggestions_for_name_error(PyNameErrorObject *exc)
return NULL;
}
+ // Are we inside a method and the instance has an attribute called 'name'?
+ int res = PySequence_Contains(dir, &_Py_ID(self));
+ if (res < 0) {
+ goto error;
+ }
+ if (res > 0) {
+ PyObject* locals = PyFrame_GetLocals(frame);
+ if (!locals) {
+ goto error;
+ }
+ PyObject* self = PyDict_GetItemWithError(locals, &_Py_ID(self)); /* borrowed */
+ if (!self) {
+ Py_DECREF(locals);
+ goto error;
+ }
+
+ PyObject *value;
+ res = _PyObject_LookupAttr(self, name, &value);
+ Py_DECREF(locals);
+ if (res < 0) {
+ goto error;
+ }
+ if (value) {
+ Py_DECREF(value);
+ Py_DECREF(dir);
+ return PyUnicode_FromFormat("self.%U", name);
+ }
+ }
+
PyObject *suggestions = calculate_suggestions(dir, name);
Py_DECREF(dir);
- if (suggestions != NULL|| PyErr_Occurred()) {
+ if (suggestions != NULL || PyErr_Occurred()) {
return suggestions;
}
@@ -259,6 +283,103 @@ offer_suggestions_for_name_error(PyNameErrorObject *exc)
Py_DECREF(dir);
return suggestions;
+
+error:
+ Py_DECREF(dir);
+ return NULL;
+}
+
+static bool
+is_name_stdlib_module(PyObject* name)
+{
+ const char* the_name = PyUnicode_AsUTF8(name);
+ Py_ssize_t len = Py_ARRAY_LENGTH(_Py_stdlib_module_names);
+ for (Py_ssize_t i = 0; i < len; i++) {
+ if (strcmp(the_name, _Py_stdlib_module_names[i]) == 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static PyObject *
+offer_suggestions_for_name_error(PyNameErrorObject *exc)
+{
+ PyObject *name = exc->name; // borrowed reference
+ PyTracebackObject *traceback = (PyTracebackObject *) exc->traceback; // borrowed reference
+ // Abort if we don't have a variable name or we have an invalid one
+ // or if we don't have a traceback to work with
+ if (name == NULL || !PyUnicode_CheckExact(name) ||
+ traceback == NULL || !Py_IS_TYPE(traceback, &PyTraceBack_Type)
+ ) {
+ return NULL;
+ }
+
+ // Move to the traceback of the exception
+ while (1) {
+ PyTracebackObject *next = traceback->tb_next;
+ if (next == NULL || !Py_IS_TYPE(next, &PyTraceBack_Type)) {
+ break;
+ }
+ else {
+ traceback = next;
+ }
+ }
+
+ PyFrameObject *frame = traceback->tb_frame;
+ assert(frame != NULL);
+
+ PyObject* suggestion = get_suggestions_for_name_error(name, frame);
+ if (suggestion == NULL && PyErr_Occurred()) {
+ return NULL;
+ }
+
+ // Add a trailer ". Did you mean: (...)?"
+ PyObject* result = NULL;
+ if (!is_name_stdlib_module(name)) {
+ if (suggestion == NULL) {
+ return NULL;
+ }
+ result = PyUnicode_FromFormat(". Did you mean: %R?", suggestion);
+ } else if (suggestion == NULL) {
+ result = PyUnicode_FromFormat(". Did you forget to import %R?", name);
+ } else {
+ result = PyUnicode_FromFormat(". Did you mean: %R? Or did you forget to import %R?", suggestion, name);
+ }
+ Py_XDECREF(suggestion);
+ return result;
+}
+
+static PyObject *
+offer_suggestions_for_import_error(PyImportErrorObject *exc)
+{
+ PyObject *mod_name = exc->name; // borrowed reference
+ PyObject *name = exc->name_from; // borrowed reference
+ if (name == NULL || mod_name == NULL || name == Py_None ||
+ !PyUnicode_CheckExact(name) || !PyUnicode_CheckExact(mod_name)) {
+ return NULL;
+ }
+
+ PyObject* mod = PyImport_GetModule(mod_name);
+ if (mod == NULL) {
+ return NULL;
+ }
+
+ PyObject *dir = PyObject_Dir(mod);
+ Py_DECREF(mod);
+ if (dir == NULL) {
+ return NULL;
+ }
+
+ PyObject *suggestion = calculate_suggestions(dir, name);
+ Py_DECREF(dir);
+ if (!suggestion) {
+ return NULL;
+ }
+
+ PyObject* result = PyUnicode_FromFormat(". Did you mean: %R?", suggestion);
+ Py_DECREF(suggestion);
+ return result;
}
// Offer suggestions for a given exception. Returns a python string object containing the
@@ -273,6 +394,8 @@ _Py_Offer_Suggestions(PyObject *exception)
result = offer_suggestions_for_attribute_error((PyAttributeErrorObject *) exception);
} else if (Py_IS_TYPE(exception, (PyTypeObject*)PyExc_NameError)) {
result = offer_suggestions_for_name_error((PyNameErrorObject *) exception);
+ } else if (Py_IS_TYPE(exception, (PyTypeObject*)PyExc_ImportError)) {
+ result = offer_suggestions_for_import_error((PyImportErrorObject *) exception);
}
return result;
}
@@ -293,6 +416,14 @@ _Py_UTF8_Edit_Cost(PyObject *a, PyObject *b, Py_ssize_t max_cost)
if (max_cost == -1) {
max_cost = MOVE_COST * Py_MAX(size_a, size_b);
}
- return levenshtein_distance(utf8_a, size_a, utf8_b, size_b, max_cost);
+ size_t *buffer = PyMem_New(size_t, MAX_STRING_SIZE);
+ if (buffer == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ Py_ssize_t res = levenshtein_distance(utf8_a, size_a,
+ utf8_b, size_b, max_cost, buffer);
+ PyMem_Free(buffer);
+ return res;
}