mirror of
				https://github.com/python/cpython.git
				synced 2025-10-26 11:14:33 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			207 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "Python.h"
 | |
| #include "frameobject.h"
 | |
| 
 | |
| #include "pycore_pyerrors.h"
 | |
| 
 | |
| #define MAX_DISTANCE 3
 | |
| #define MAX_CANDIDATE_ITEMS 160
 | |
| #define MAX_STRING_SIZE 25
 | |
| 
 | |
| /* Calculate the Levenshtein distance between string1 and string2 */
 | |
| static Py_ssize_t
 | |
| levenshtein_distance(const char *a, size_t a_size,
 | |
|                      const char *b, size_t b_size) {
 | |
| 
 | |
|     if (a_size > MAX_STRING_SIZE || b_size > MAX_STRING_SIZE) {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     // Both strings are the same (by identity)
 | |
|     if (a == b) {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     // The first string is empty
 | |
|     if (a_size == 0) {
 | |
|         return b_size;
 | |
|     }
 | |
| 
 | |
|     // The second string is empty
 | |
|     if (b_size == 0) {
 | |
|         return a_size;
 | |
|     }
 | |
| 
 | |
|     size_t *buffer = PyMem_Calloc(a_size, sizeof(size_t));
 | |
|     if (buffer == NULL) {
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     // Initialize the buffer row
 | |
|     size_t index = 0;
 | |
|     while (index < a_size) {
 | |
|         buffer[index] = index + 1;
 | |
|         index++;
 | |
|     }
 | |
| 
 | |
|     size_t b_index = 0;
 | |
|     size_t result = 0;
 | |
|     while (b_index < b_size) {
 | |
|         char code = b[b_index];
 | |
|         size_t distance = result = b_index++;
 | |
|         index = SIZE_MAX;
 | |
|         while (++index < a_size) {
 | |
|             size_t b_distance = code == a[index] ? distance : distance + 1;
 | |
|             distance = buffer[index];
 | |
|             if (distance > result) {
 | |
|                 if (b_distance > result) {
 | |
|                     result = result + 1;
 | |
|                 } else {
 | |
|                     result = b_distance;
 | |
|                 }
 | |
|             } else {
 | |
|                 if (b_distance > distance) {
 | |
|                     result = distance + 1;
 | |
|                 } else {
 | |
|                     result = b_distance;
 | |
|                 }
 | |
|             }
 | |
|             buffer[index] = result;
 | |
|         }
 | |
|     }
 | |
|     PyMem_Free(buffer);
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| static inline PyObject *
 | |
| calculate_suggestions(PyObject *dir,
 | |
|                       PyObject *name) {
 | |
|     assert(!PyErr_Occurred());
 | |
|     assert(PyList_CheckExact(dir));
 | |
| 
 | |
|     Py_ssize_t dir_size = PyList_GET_SIZE(dir);
 | |
|     if (dir_size >= MAX_CANDIDATE_ITEMS) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     Py_ssize_t suggestion_distance = PyUnicode_GetLength(name);
 | |
|     PyObject *suggestion = NULL;
 | |
|     Py_ssize_t name_size;
 | |
|     const char *name_str = PyUnicode_AsUTF8AndSize(name, &name_size);
 | |
|     if (name_str == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
|     for (int i = 0; i < dir_size; ++i) {
 | |
|         PyObject *item = PyList_GET_ITEM(dir, i);
 | |
|         Py_ssize_t item_size;
 | |
|         const char *item_str = PyUnicode_AsUTF8AndSize(item, &item_size);
 | |
|         if (item_str == NULL) {
 | |
|             return NULL;
 | |
|         }
 | |
|         Py_ssize_t current_distance = levenshtein_distance(
 | |
|                 name_str, name_size, item_str, item_size);
 | |
|         if (current_distance == -1) {
 | |
|             return NULL;
 | |
|         }
 | |
|         if (current_distance == 0 ||
 | |
|             current_distance > MAX_DISTANCE ||
 | |
|             current_distance * 2 > name_size)
 | |
|         {
 | |
|             continue;
 | |
|         }
 | |
|         if (!suggestion || current_distance < suggestion_distance) {
 | |
|             suggestion = item;
 | |
|             suggestion_distance = current_distance;
 | |
|         }
 | |
|     }
 | |
|     if (!suggestion) {
 | |
|         return NULL;
 | |
|     }
 | |
|     Py_INCREF(suggestion);
 | |
|     return suggestion;
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc) {
 | |
|     PyObject *name = exc->name; // borrowed reference
 | |
|     PyObject *obj = exc->obj; // borrowed reference
 | |
| 
 | |
|     // Abort if we don't have an attribute name or we have an invalid one
 | |
|     if (name == NULL || obj == NULL || !PyUnicode_CheckExact(name)) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     PyObject *dir = PyObject_Dir(obj);
 | |
|     if (dir == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     PyObject *suggestions = calculate_suggestions(dir, name);
 | |
|     Py_DECREF(dir);
 | |
|     return suggestions;
 | |
| }
 | |
| 
 | |
| 
 | |
| 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 || traceback == NULL || !PyUnicode_CheckExact(name)) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     // Move to the traceback of the exception
 | |
|     while (traceback->tb_next != NULL) {
 | |
|         traceback = traceback->tb_next;
 | |
|     }
 | |
| 
 | |
|     PyFrameObject *frame = traceback->tb_frame;
 | |
|     assert(frame != NULL);
 | |
|     PyCodeObject *code = frame->f_code;
 | |
|     assert(code != NULL && code->co_varnames != NULL);
 | |
|     PyObject *dir = PySequence_List(code->co_varnames);
 | |
|     if (dir == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     PyObject *suggestions = calculate_suggestions(dir, name);
 | |
|     Py_DECREF(dir);
 | |
|     if (suggestions != NULL) {
 | |
|         return suggestions;
 | |
|     }
 | |
| 
 | |
|     dir = PySequence_List(frame->f_globals);
 | |
|     if (dir == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
|     suggestions = calculate_suggestions(dir, name);
 | |
|     Py_DECREF(dir);
 | |
|     if (suggestions != NULL) {
 | |
|         return suggestions;
 | |
|     }
 | |
| 
 | |
|     dir = PySequence_List(frame->f_builtins);
 | |
|     if (dir == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
|     suggestions = calculate_suggestions(dir, name);
 | |
|     Py_DECREF(dir);
 | |
| 
 | |
|     return suggestions;
 | |
| }
 | |
| 
 | |
| // Offer suggestions for a given exception. Returns a python string object containing the
 | |
| // suggestions. This function returns NULL if no suggestion was found or if an exception happened,
 | |
| // users must call PyErr_Occurred() to disambiguate.
 | |
| PyObject *_Py_Offer_Suggestions(PyObject *exception) {
 | |
|     PyObject *result = NULL;
 | |
|     assert(!PyErr_Occurred());
 | |
|     if (Py_IS_TYPE(exception, (PyTypeObject*)PyExc_AttributeError)) {
 | |
|         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);
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| 
 | 
