3
3
4
4
#include "pycore_pyerrors.h"
5
5
#include "pycore_code.h" // _PyCode_GetVarnames()
6
+ #include "stdlib_module_names.h" // _Py_stdlib_module_names
6
7
7
8
#define MAX_CANDIDATE_ITEMS 750
8
9
#define MAX_STRING_SIZE 40
@@ -175,7 +176,7 @@ calculate_suggestions(PyObject *dir,
175
176
}
176
177
177
178
static PyObject *
178
- offer_suggestions_for_attribute_error (PyAttributeErrorObject * exc )
179
+ get_suggestions_for_attribute_error (PyAttributeErrorObject * exc )
179
180
{
180
181
PyObject * name = exc -> name ; // borrowed reference
181
182
PyObject * obj = exc -> obj ; // borrowed reference
@@ -195,35 +196,23 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc)
195
196
return suggestions ;
196
197
}
197
198
198
-
199
199
static PyObject *
200
- offer_suggestions_for_name_error ( PyNameErrorObject * exc )
200
+ offer_suggestions_for_attribute_error ( PyAttributeErrorObject * exc )
201
201
{
202
- PyObject * name = exc -> name ; // borrowed reference
203
- PyTracebackObject * traceback = (PyTracebackObject * ) exc -> traceback ; // borrowed reference
204
- // Abort if we don't have a variable name or we have an invalid one
205
- // or if we don't have a traceback to work with
206
- if (name == NULL || !PyUnicode_CheckExact (name ) ||
207
- traceback == NULL || !Py_IS_TYPE (traceback , & PyTraceBack_Type )
208
- ) {
202
+ PyObject * suggestion = get_suggestions_for_attribute_error (exc );
203
+ if (suggestion == NULL ) {
209
204
return NULL ;
210
205
}
206
+ // Add a trailer ". Did you mean: (...)?"
207
+ return PyUnicode_FromFormat (". Did you mean %R?" , suggestion );
208
+ }
211
209
212
- // Move to the traceback of the exception
213
- while (1 ) {
214
- PyTracebackObject * next = traceback -> tb_next ;
215
- if (next == NULL || !Py_IS_TYPE (next , & PyTraceBack_Type )) {
216
- break ;
217
- }
218
- else {
219
- traceback = next ;
220
- }
221
- }
222
-
223
- PyFrameObject * frame = traceback -> tb_frame ;
224
- assert (frame != NULL );
210
+ static PyObject *
211
+ get_suggestions_for_name_error (PyObject * name , PyFrameObject * frame )
212
+ {
225
213
PyCodeObject * code = PyFrame_GetCode (frame );
226
214
assert (code != NULL && code -> co_localsplusnames != NULL );
215
+
227
216
PyObject * varnames = _PyCode_GetVarnames (code );
228
217
if (varnames == NULL ) {
229
218
return NULL ;
@@ -261,6 +250,66 @@ offer_suggestions_for_name_error(PyNameErrorObject *exc)
261
250
return suggestions ;
262
251
}
263
252
253
+ static bool
254
+ is_name_stdlib_module (PyObject * name )
255
+ {
256
+ const char * the_name = PyUnicode_AsUTF8 (name );
257
+ Py_ssize_t len = Py_ARRAY_LENGTH (_Py_stdlib_module_names );
258
+ for (Py_ssize_t i = 0 ; i < len ; i ++ ) {
259
+ if (strcmp (the_name , _Py_stdlib_module_names [i ]) == 0 ) {
260
+ return 1 ;
261
+ }
262
+ }
263
+ return 0 ;
264
+ }
265
+
266
+ static PyObject *
267
+ offer_suggestions_for_name_error (PyNameErrorObject * exc )
268
+ {
269
+ PyObject * name = exc -> name ; // borrowed reference
270
+ PyTracebackObject * traceback = (PyTracebackObject * ) exc -> traceback ; // borrowed reference
271
+ // Abort if we don't have a variable name or we have an invalid one
272
+ // or if we don't have a traceback to work with
273
+ if (name == NULL || !PyUnicode_CheckExact (name ) ||
274
+ traceback == NULL || !Py_IS_TYPE (traceback , & PyTraceBack_Type )
275
+ ) {
276
+ return NULL ;
277
+ }
278
+
279
+ // Move to the traceback of the exception
280
+ while (1 ) {
281
+ PyTracebackObject * next = traceback -> tb_next ;
282
+ if (next == NULL || !Py_IS_TYPE (next , & PyTraceBack_Type )) {
283
+ break ;
284
+ }
285
+ else {
286
+ traceback = next ;
287
+ }
288
+ }
289
+
290
+ PyFrameObject * frame = traceback -> tb_frame ;
291
+ assert (frame != NULL );
292
+
293
+ PyObject * suggestion = get_suggestions_for_name_error (name , frame );
294
+ bool is_stdlib_module = is_name_stdlib_module (name );
295
+
296
+ if (suggestion == NULL && !is_stdlib_module ) {
297
+ return NULL ;
298
+ }
299
+
300
+ // Add a trailer ". Did you mean: (...)?"
301
+ PyObject * result = NULL ;
302
+ if (!is_stdlib_module ) {
303
+ result = PyUnicode_FromFormat (". Did you mean %R?" , suggestion );
304
+ } else if (suggestion == NULL ) {
305
+ result = PyUnicode_FromFormat (". Did you forget to import %R?" , name );
306
+ } else {
307
+ result = PyUnicode_FromFormat (". Did you mean %R? Or did you forget to import %R?" , suggestion , name );
308
+ }
309
+ Py_XDECREF (suggestion );
310
+ return result ;
311
+ }
312
+
264
313
// Offer suggestions for a given exception. Returns a python string object containing the
265
314
// suggestions. This function returns NULL if no suggestion was found or if an exception happened,
266
315
// users must call PyErr_Occurred() to disambiguate.
0 commit comments