@@ -1113,9 +1113,6 @@ add_keyword_typo(KeywordTypo *candidates, int *count, int mark,
11131113 if (word == NULL ) {
11141114 return -1 ;
11151115 }
1116- if (PyUnicode_GET_LENGTH (name ) <= 2 ) {
1117- max_cost = Py_MIN (max_cost , 2 );
1118- }
11191116 Py_ssize_t cost = _Py_UTF8_Edit_Cost (name , word , max_cost );
11201117 // Adjacent transpositions are one spelling error. The shared Levenshtein
11211118 // distance counts them as two substitutions.
@@ -1186,17 +1183,20 @@ keyword_trial_accepts(Parser *p, const KeywordTypo *candidate)
11861183static int
11871184try_keyword_typo (Parser * p , const KeywordTypo * candidate , KeywordTrial * state )
11881185{
1186+ PyArena * arena = _PyArena_New ();
1187+ if (arena == NULL ) {
1188+ return -1 ;
1189+ }
11891190 // Token text belongs to the original arena. Token headers and memo entries
11901191 // must be private: even a failed trial can memoize nodes containing its edit.
1191- Token * tokens = PyMem_Malloc ( p -> fill * sizeof (* tokens ));
1192- Token * * index = PyMem_Malloc ( p -> fill * sizeof (* index ));
1192+ Token * tokens = _PyArena_Malloc ( arena , p -> fill * sizeof (* tokens ));
1193+ Token * * index = _PyArena_Malloc ( arena , p -> fill * sizeof (* index ));
11931194 Parser trial = * p ;
11941195 trial .identifier_cache = p -> identifier_cache == NULL ? NULL :
11951196 PyMem_Malloc (IDENTIFIER_CACHE_SIZE * sizeof (* trial .identifier_cache ));
1196- PyArena * arena = _PyArena_New ();
11971197 PyObject * replacement = PyBytes_FromString (candidate -> keyword );
11981198 int accepted = -1 ;
1199- if (tokens == NULL || index == NULL || arena == NULL || replacement == NULL ||
1199+ if (tokens == NULL || index == NULL || replacement == NULL ||
12001200 (p -> identifier_cache != NULL && trial .identifier_cache == NULL ))
12011201 {
12021202 goto done ;
@@ -1240,14 +1240,10 @@ try_keyword_typo(Parser *p, const KeywordTypo *candidate, KeywordTrial *state)
12401240 accepted = keyword_trial_accepts (& trial , candidate );
12411241done :
12421242 // No Parser_Free: the trial borrows the tokenizer and type-ignore comments,
1243- // and its token headers are a single allocation .
1243+ // and its token arrays belong to the arena .
12441244 PyMem_Free (trial .identifier_cache );
1245- if (arena != NULL ) {
1246- _PyArena_Free (arena );
1247- }
1245+ _PyArena_Free (arena );
12481246 Py_XDECREF (replacement );
1249- PyMem_Free (index );
1250- PyMem_Free (tokens );
12511247 return PyErr_Occurred () ? -1 : accepted ;
12521248}
12531249
@@ -1358,7 +1354,8 @@ collect_keyword_typos(Parser *p, PySyntaxErrorObject *error, KeywordTypo *candid
13581354 // Preserve missing-comma diagnostics and correctly spelled keywords
13591355 // unless one edit repairs the input. For keywords, only substitute a
13601356 // character: deleting one from "for" must not suggest "or".
1361- Py_ssize_t max_cost = p -> missing_comma || is_keyword ? 2 : 4 ;
1357+ Py_ssize_t max_cost =
1358+ p -> missing_comma || is_keyword || PyUnicode_GET_LENGTH (name ) <= 2 ? 2 : 4 ;
13621359 int result = 0 ;
13631360 for (int length = 0 ; length < p -> n_keyword_lists && result == 0 ; length ++ ) {
13641361 if (is_keyword && length != text_length ) {
@@ -1385,32 +1382,43 @@ collect_keyword_typos(Parser *p, PySyntaxErrorObject *error, KeywordTypo *candid
13851382}
13861383
13871384static PyObject *
1388- build_keyword_typo_metadata (Parser * p , const KeywordTypo * candidate )
1385+ build_keyword_typo_metadata (Parser * p , PySyntaxErrorObject * error ,
1386+ const KeywordTypo * candidate )
13891387{
13901388 Token * token = p -> tokens [candidate -> mark ];
1391- PyObject * line ;
1389+ PyObject * line = NULL ;
13921390 if (_PyTokenizer_RetainedSource (p -> tok ) != NULL ) {
13931391 Py_ssize_t size ;
13941392 const char * text = _PyTokenizer_LineView (p -> tok , token -> lineno , & size );
13951393 line = PyUnicode_DecodeUTF8 (text , size , "replace" );
13961394 }
1397- else {
1398- _PyTokenizer_Info info = _PyTokenizer_GetInfo ( p -> tok );
1399- line = _PyErr_ProgramDecodedTextObject (
1400- info . filename , token -> lineno , info . encoding );
1395+ else if ( PyLong_AsLong ( error -> lineno ) == token -> lineno &&
1396+ error -> text != NULL && PyUnicode_CheckExact ( error -> text )) {
1397+ // Reuse the error's line without another file access or codec lookup.
1398+ line = Py_NewRef ( error -> text );
14011399 }
1402- if (line == NULL ) {
1400+ if (line == NULL && PyErr_Occurred () ) {
14031401 return NULL ;
14041402 }
1405- Py_ssize_t start = _PyPegen_byte_offset_to_character_offset (line , token -> col_offset );
1406- Py_ssize_t end = _PyPegen_byte_offset_to_character_offset (line , token -> end_col_offset );
1407- PyObject * metadata = NULL ;
1408- if (start >= 0 && end >= 0 ) {
1409- metadata = Py_BuildValue ("(sOinin)" , candidate -> keyword , line ,
1410- token -> lineno , start + 1 ,
1411- token -> end_lineno , end + 1 );
1403+ PyObject * location = NULL ;
1404+ if (line != NULL ) {
1405+ Py_ssize_t start = _PyPegen_byte_offset_to_character_offset (line , token -> col_offset );
1406+ Py_ssize_t end = _PyPegen_byte_offset_to_character_offset (line , token -> end_col_offset );
1407+ if (start >= 0 && end >= 0 ) {
1408+ location = Py_BuildValue ("(Oinin)" , line , token -> lineno , start + 1 ,
1409+ token -> end_lineno , end + 1 );
1410+ }
1411+ Py_DECREF (line );
1412+ if (location == NULL ) {
1413+ return NULL ;
1414+ }
14121415 }
1413- Py_DECREF (line );
1416+ // Without the typo's source line, retain the original error location.
1417+ PyObject * metadata = Py_BuildValue (
1418+ "(sO(OOOOOOO))" , candidate -> keyword , location != NULL ? location : Py_None ,
1419+ error -> filename , error -> msg , error -> text , error -> lineno , error -> offset ,
1420+ error -> end_lineno , error -> end_offset );
1421+ Py_XDECREF (location );
14141422 return metadata ;
14151423}
14161424
@@ -1451,7 +1459,7 @@ keyword_typo_metadata(Parser *p, PySyntaxErrorObject *error)
14511459 return NULL ;
14521460 }
14531461 if (accepted ) {
1454- return build_keyword_typo_metadata (p , & candidates [i ]);
1462+ return build_keyword_typo_metadata (p , error , & candidates [i ]);
14551463 }
14561464 }
14571465 return NULL ;
0 commit comments