@@ -1122,7 +1122,7 @@ add_keyword_typo(KeywordTypo *candidates, int *count, int mark,
11221122}
11231123
11241124static int
1125- try_keyword_typo (Parser * p , KeywordTypo * candidate )
1125+ try_keyword_typo (Parser * p , KeywordTypo * candidate , KeywordTrial * state )
11261126{
11271127 // Token text belongs to the original arena. Token headers and memo entries
11281128 // must be private: even a failed trial can memoize nodes containing its edit.
@@ -1159,7 +1159,8 @@ try_keyword_typo(Parser *p, KeywordTypo *candidate)
11591159 p , candidate -> keyword , strlen (candidate -> keyword ));
11601160 edited -> bytes = replacement ;
11611161
1162- KeywordTrial state = {.budget = MAX_KEYWORD_TYPO_STEPS };
1162+ state -> furthest = 0 ;
1163+ state -> incomplete_suite = 0 ;
11631164 Parser trial = * p ;
11641165 trial .tokens = index ;
11651166 trial .arena = arena ;
@@ -1168,7 +1169,7 @@ try_keyword_typo(Parser *p, KeywordTypo *candidate)
11681169 trial .level = 0 ;
11691170 trial .error_indicator = 0 ;
11701171 trial .call_invalid_rules = 0 ;
1171- trial .keyword_trial = & state ;
1172+ trial .keyword_trial = state ;
11721173 // Force trials through the existing stack-check slow path to count rule
11731174 // entries without adding a branch to normal parsing. The slow path still
11741175 // checks the thread's real stack limit.
@@ -1192,7 +1193,7 @@ try_keyword_typo(Parser *p, KeywordTypo *candidate)
11921193 if (!accepted && !trial .error_indicator && candidate -> is_alias &&
11931194 p -> feature_version == PY_MINOR_VERSION )
11941195 {
1195- for (int i = candidate -> mark ; i < state . furthest ; i ++ ) {
1196+ for (int i = candidate -> mark ; i < state -> furthest ; i ++ ) {
11961197 if (tokens [i ].type == NEWLINE ) {
11971198 accepted = 1 ;
11981199 break ;
@@ -1204,7 +1205,7 @@ try_keyword_typo(Parser *p, KeywordTypo *candidate)
12041205 _PyPegen_parse (& trial );
12051206 // Version checks on an enclosing rule can run after its suite. For
12061207 // older grammars, require a complete repair so those checks execute.
1207- accepted = state . incomplete_suite && p -> feature_version == PY_MINOR_VERSION ;
1208+ accepted = state -> incomplete_suite && p -> feature_version == PY_MINOR_VERSION ;
12081209 }
12091210 // No Parser_Free: the trial borrows the tokenizer and type-ignore comments,
12101211 // and its token headers are a single allocation.
@@ -1224,6 +1225,7 @@ keyword_typo_metadata(Parser *p, PySyntaxErrorObject *error)
12241225 if ((p -> start_rule != Py_file_input && p -> start_rule != Py_eval_input &&
12251226 p -> start_rule != Py_single_input ) ||
12261227 p -> fill == 0 || p -> fill > MAX_KEYWORD_TYPO_TOKENS ||
1228+ _PyTokenizer_GetInfo (p -> tok ).input_span .end > MAX_KEYWORD_TYPO_BYTES ||
12271229 p -> tokens [p -> fill - 1 ]-> type != ENDMARKER ||
12281230 _PyTokenizer_IsInteractive (p -> tok ) || !PyUnicode_Check (error -> msg ))
12291231 {
@@ -1301,8 +1303,10 @@ keyword_typo_metadata(Parser *p, PySyntaxErrorObject *error)
13011303 }
13021304 }
13031305 qsort (candidates , count , sizeof (* candidates ), compare_keyword_typos );
1306+ // All candidates and both parser passes share this work budget.
1307+ KeywordTrial state = {.budget = MAX_KEYWORD_TYPO_STEPS };
13041308 for (int i = 0 ; i < count ; i ++ ) {
1305- int accepted = try_keyword_typo (p , & candidates [i ]);
1309+ int accepted = try_keyword_typo (p , & candidates [i ], & state );
13061310 if (accepted < 0 ) {
13071311 return NULL ;
13081312 }
0 commit comments