| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -150,7 +150,7 @@ extern PyObject* _PyExc_PrepReraiseStar( | |||
| 150 | 150 | extern int _PyErr_CheckSignalsTstate(PyThreadState *tstate); | |
| 151 | 151 | ||
| 152 | 152 | extern void _Py_DumpExtensionModules(int fd, PyInterpreterState *interp); | |
| 153 | - | ||
| 153 | + extern PyObject* _Py_CalculateSuggestions(PyObject *dir, PyObject *name); | ||
| 154 | 154 | extern PyObject* _Py_Offer_Suggestions(PyObject* exception); | |
| 155 | 155 | // Export for '_testinternalcapi' shared extension | |
| 156 | 156 | PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -915,6 +915,74 @@ def test_multiple_values(self): | |||
| 915 | 915 | with self.check_raises_type_error(msg): | |
| 916 | 916 | A().method_two_args("x", "y", x="oops") | |
| 917 | 917 | ||
| 918 | + @cpython_only | ||
| 919 | + class TestErrorMessagesSuggestions(unittest.TestCase): | ||
| 920 | + @contextlib.contextmanager | ||
| 921 | + def check_suggestion_includes(self, message): | ||
| 922 | + with self.assertRaises(TypeError) as cm: | ||
| 923 | + yield | ||
| 924 | + self.assertIn(f"Did you mean '{message}'?", str(cm.exception)) | ||
| 925 | + | ||
| 926 | + @contextlib.contextmanager | ||
| 927 | + def check_suggestion_not_pressent(self): | ||
| 928 | + with self.assertRaises(TypeError) as cm: | ||
| 929 | + yield | ||
| 930 | + self.assertNotIn("Did you mean", str(cm.exception)) | ||
| 931 | + | ||
| 932 | + def test_unexpected_keyword_suggestion_valid_positions(self): | ||
| 933 | + def foo(blech=None, /, aaa=None, *args, late1=None): | ||
| 934 | + pass | ||
| 935 | + | ||
| 936 | + cases = [ | ||
| 937 | + ("blach", None), | ||
| 938 | + ("aa", "aaa"), | ||
| 939 | + ("orgs", None), | ||
| 940 | + ("late11", "late1"), | ||
| 941 | + ] | ||
| 942 | + | ||
| 943 | + for keyword, suggestion in cases: | ||
| 944 | + with self.subTest(keyword): | ||
| 945 | + ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_pressent() | ||
| 946 | + with ctx: | ||
| 947 | + foo(**{keyword:None}) | ||
| 948 | + | ||
| 949 | + def test_unexpected_keyword_suggestion_kinds(self): | ||
| 950 | + | ||
| 951 | + def substitution(noise=None, more_noise=None, a = None, blech = None): | ||
| 952 | + pass | ||
| 953 | + | ||
| 954 | + def elimination(noise = None, more_noise = None, a = None, blch = None): | ||
| 955 | + pass | ||
| 956 | + | ||
| 957 | + def addition(noise = None, more_noise = None, a = None, bluchin = None): | ||
| 958 | + pass | ||
| 959 | + | ||
| 960 | + def substitution_over_elimination(blach = None, bluc = None): | ||
| 961 | + pass | ||
| 962 | + | ||
| 963 | + def substitution_over_addition(blach = None, bluchi = None): | ||
| 964 | + pass | ||
| 965 | + | ||
| 966 | + def elimination_over_addition(bluc = None, blucha = None): | ||
| 967 | + pass | ||
| 968 | + | ||
| 969 | + def case_change_over_substitution(BLuch=None, Luch = None, fluch = None): | ||
| 970 | + pass | ||
| 971 | + | ||
| 972 | + for func, suggestion in [ | ||
| 973 | + (addition, "bluchin"), | ||
| 974 | + (substitution, "blech"), | ||
| 975 | + (elimination, "blch"), | ||
| 976 | + (addition, "bluchin"), | ||
| 977 | + (substitution_over_elimination, "blach"), | ||
| 978 | + (substitution_over_addition, "blach"), | ||
| 979 | + (elimination_over_addition, "bluc"), | ||
| 980 | + (case_change_over_substitution, "BLuch"), | ||
| 981 | + ]: | ||
| 982 | + with self.subTest(suggestion): | ||
| 983 | + with self.check_suggestion_includes(suggestion): | ||
| 984 | + func(bluch=None) | ||
| 985 | + | ||
| 918 | 986 | @cpython_only | |
| 919 | 987 | class TestRecursion(unittest.TestCase): | |
| 920 | 988 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Improve error message for function calls with bad keyword arguments. Patch | ||
| 2 | + by Pablo Galindo | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ | |||
| 26 | 26 | #include "pycore_tuple.h" // _PyTuple_ITEMS() | |
| 27 | 27 | #include "pycore_typeobject.h" // _PySuper_Lookup() | |
| 28 | 28 | #include "pycore_uops.h" // _PyUOpExecutorObject | |
| 29 | + #include "pycore_pyerrors.h" | ||
| 29 | 30 | ||
| 30 | 31 | #include "pycore_dict.h" | |
| 31 | 32 | #include "dictobject.h" | |
@@ -1327,9 +1328,33 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func, | |||
| 1327 | 1328 | goto kw_fail; | |
| 1328 | 1329 | } | |
| 1329 | 1330 | ||
| 1330 | - _PyErr_Format(tstate, PyExc_TypeError, | ||
| 1331 | - "%U() got an unexpected keyword argument '%S'", | ||
| 1332 | - func->func_qualname, keyword); | ||
| 1331 | + PyObject* suggestion_keyword = NULL; | ||
| 1332 | + if (total_args > co->co_posonlyargcount) { | ||
| 1333 | + PyObject* possible_keywords = PyList_New(total_args - co->co_posonlyargcount); | ||
| 1334 | + | ||
| 1335 | + if (!possible_keywords) { | ||
| 1336 | + PyErr_Clear(); | ||
| 1337 | + } else { | ||
| 1338 | + for (ssize_t k = co->co_posonlyargcount; k < total_args; k++) { | ||
| 1339 | + PyList_SET_ITEM(possible_keywords, k - co->co_posonlyargcount, co_varnames[k]); | ||
| 1340 | + } | ||
| 1341 | + | ||
| 1342 | + suggestion_keyword = _Py_CalculateSuggestions(possible_keywords, keyword); | ||
| 1343 | + Py_DECREF(possible_keywords); | ||
| 1344 | + | ||
| 1345 | + if (suggestion_keyword) { | ||
| 1346 | + _PyErr_Format(tstate, PyExc_TypeError, | ||
| 1347 | + "%U() got an unexpected keyword argument '%S'. Did you mean '%S'?", | ||
| 1348 | + func->func_qualname, keyword, suggestion_keyword); | ||
| 1349 | + Py_DECREF(suggestion_keyword); | ||
| 1350 | + } else { | ||
| 1351 | + _PyErr_Format(tstate, PyExc_TypeError, | ||
| 1352 | + "%U() got an unexpected keyword argument '%S'", | ||
| 1353 | + func->func_qualname, keyword); | ||
| 1354 | + } | ||
| 1355 | + } | ||
| 1356 | + } | ||
| 1357 | + | ||
| 1333 | 1358 | goto kw_fail; | |
| 1334 | 1359 | } | |
| 1335 | 1360 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -126,8 +126,8 @@ levenshtein_distance(const char *a, size_t a_size, | |||
| 126 | 126 | return result; | |
| 127 | 127 | } | |
| 128 | 128 | ||
| 129 | - static inline PyObject * | ||
| 130 | - calculate_suggestions(PyObject *dir, | ||
| 129 | + PyObject * | ||
| 130 | + _Py_CalculateSuggestions(PyObject *dir, | ||
| 131 | 131 | PyObject *name) | |
| 132 | 132 | { | |
| 133 | 133 | assert(!PyErr_Occurred()); | |
@@ -195,7 +195,7 @@ get_suggestions_for_attribute_error(PyAttributeErrorObject *exc) | |||
| 195 | 195 | return NULL; | |
| 196 | 196 | } | |
| 197 | 197 | ||
| 198 | - PyObject *suggestions = calculate_suggestions(dir, name); | ||
| 198 | + PyObject *suggestions = _Py_CalculateSuggestions(dir, name); | ||
| 199 | 199 | Py_DECREF(dir); | |
| 200 | 200 | return suggestions; | |
| 201 | 201 | } | |
@@ -259,7 +259,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) | |||
| 259 | 259 | } | |
| 260 | 260 | } | |
| 261 | 261 | ||
| 262 | - PyObject *suggestions = calculate_suggestions(dir, name); | ||
| 262 | + PyObject *suggestions = _Py_CalculateSuggestions(dir, name); | ||
| 263 | 263 | Py_DECREF(dir); | |
| 264 | 264 | if (suggestions != NULL || PyErr_Occurred()) { | |
| 265 | 265 | return suggestions; | |
@@ -269,7 +269,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) | |||
| 269 | 269 | if (dir == NULL) { | |
| 270 | 270 | return NULL; | |
| 271 | 271 | } | |
| 272 | - suggestions = calculate_suggestions(dir, name); | ||
| 272 | + suggestions = _Py_CalculateSuggestions(dir, name); | ||
| 273 | 273 | Py_DECREF(dir); | |
| 274 | 274 | if (suggestions != NULL || PyErr_Occurred()) { | |
| 275 | 275 | return suggestions; | |
@@ -279,7 +279,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) | |||
| 279 | 279 | if (dir == NULL) { | |
| 280 | 280 | return NULL; | |
| 281 | 281 | } | |
| 282 | - suggestions = calculate_suggestions(dir, name); | ||
| 282 | + suggestions = _Py_CalculateSuggestions(dir, name); | ||
| 283 | 283 | Py_DECREF(dir); | |
| 284 | 284 | ||
| 285 | 285 | return suggestions; | |
@@ -371,7 +371,7 @@ offer_suggestions_for_import_error(PyImportErrorObject *exc) | |||
| 371 | 371 | return NULL; | |
| 372 | 372 | } | |
| 373 | 373 | ||
| 374 | - PyObject *suggestion = calculate_suggestions(dir, name); | ||
| 374 | + PyObject *suggestion = _Py_CalculateSuggestions(dir, name); | ||
| 375 | 375 | Py_DECREF(dir); | |
| 376 | 376 | if (!suggestion) { | |
| 377 | 377 | return NULL; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments