@@ -195,6 +195,28 @@ test_tokenizer_source(PyObject *Py_UNUSED(module),
195195 goto error ;
196196 }
197197
198+ _PyTok_SourceDiscard (& source );
199+ if (check (_PyTok_SourceAppendLine (& source , "a\n" , 2 , 0 ) == 4 ,
200+ "wrong retained source offset" ) < 0 ||
201+ _PyTok_SourceLine (& source , 1 , & line ) < 0 ||
202+ check (line .start == 4 && line .end == 6 ,
203+ "wrong retained source line" ) < 0 ||
204+ _PyTok_SourceLocation (
205+ & source , 4 , _PYTOK_AFFINITY_LEFT , & loc ) < 0 ||
206+ check (loc .lineno == 1 && loc .byte_col == 0 ,
207+ "wrong retained source location" ) < 0 ) {
208+ goto error ;
209+ }
210+ view = _PyTok_SourceSpanView (
211+ & source , _PyTok_SpanFromBounds (4 , 5 ), & view_len );
212+ if (check (view != NULL && view_len == 1 && view [0 ] == 'a' ,
213+ "wrong retained source span" ) < 0 ||
214+ check_system_error (_PyTok_SourceSpanView (
215+ & source , _PyTok_SpanFromBounds (0 , 1 ), & view_len ) == NULL ,
216+ "accepted discarded source span" ) < 0 ) {
217+ goto error ;
218+ }
219+
198220 _PyTok_SourceClear (& source );
199221 Py_RETURN_NONE ;
200222
@@ -296,6 +318,88 @@ test_tokenizer_cursor(PyObject *Py_UNUSED(module),
296318 }
297319#endif
298320
321+ _PyTok_Off base = source .len ;
322+ _PyTok_SourceDiscard (& source );
323+ if (_PyTok_SourceAppendLine (& source , "ab\n" , 3 , 0 ) < 0 ||
324+ _PyTok_SourceAppendLine (& source , "cd" , 2 , 0 ) < 0 ) {
325+ goto error ;
326+ }
327+ _PyTok_CursorInit (& cursor , & source );
328+ if (_PyTok_CursorSetLine (& cursor , 1 ) < 0 ||
329+ check (cursor .pos == base && _PyTok_CursorPeek (& cursor , 1 ) == 'b' ,
330+ "wrong retained cursor line" ) < 0 ||
331+ _PyTok_CursorSetLine (& cursor , 2 ) < 0 ||
332+ check (_PyTok_CursorAdvance (& cursor ) == 'c' ,
333+ "wrong retained cursor byte" ) < 0 ||
334+ _PyTok_CursorSetOffset (& cursor , base + 5 ) < 0 ||
335+ check (cursor .lineno == 2 && _PyTok_CursorAdvance (& cursor ) == EOF ,
336+ "wrong retained cursor EOF" ) < 0 ) {
337+ goto error ;
338+ }
339+
340+ _PyTok_SourceClear (& source );
341+ Py_RETURN_NONE ;
342+
343+ error :
344+ _PyTok_SourceClear (& source );
345+ return NULL ;
346+ }
347+
348+ static PyObject *
349+ test_tokenizer_source_discard (PyObject * Py_UNUSED (module ),
350+ PyObject * Py_UNUSED (args ))
351+ {
352+ _PyTok_SourceText source ;
353+ _PyTok_SourceInit (& source );
354+ for (int i = 0 ; i < 260 ; i ++ ) {
355+ if (_PyTok_SourceAppendLine (& source , "x\n" , 2 , 1 ) < 0 ) {
356+ goto error ;
357+ }
358+ }
359+ char * bytes = source .bytes ;
360+ _PyTok_Off capacity = source .cap ;
361+ _PyTok_SourceDiscard (& source );
362+ if (check (source .base_offset == 520 && source .len == 0 &&
363+ source .nlines == 0 && source .bytes == bytes &&
364+ source .cap == capacity && source .bytes [0 ] == '\0' ,
365+ "discard did not preserve source allocation" ) < 0 ) {
366+ goto error ;
367+ }
368+ for (int i = 0 ; i < 260 ; i ++ ) {
369+ if (check (_PyTok_SourceAppendLine (& source , "y\n" , 2 , 0 ) == 520 + 2 * i ,
370+ "wrong source offset after discard" ) < 0 ||
371+ check (!_PyTok_SourceLineIsImplicit (& source , i + 1 ),
372+ "discard preserved implicit newline flag" ) < 0 ) {
373+ goto error ;
374+ }
375+ }
376+ if (check (source .bytes == bytes && source .cap == capacity ,
377+ "discarded allocation was not reused" ) < 0 ) {
378+ goto error ;
379+ }
380+ _PyTok_SourceDiscard (& source );
381+ if (check (_PyTok_SourceAppendLine (& source , "tail" , 4 , 0 ) == 1040 ,
382+ "wrong source offset after repeated discard" ) < 0 ) {
383+ goto error ;
384+ }
385+ _PyTok_SourceDiscard (& source );
386+ if (check (_PyTok_SourceAppendLine (& source , "z\n" , 2 , 0 ) == 1044 ,
387+ "cannot append after discarding unterminated line" ) < 0 ) {
388+ goto error ;
389+ }
390+ _PyTok_SourceDiscard (& source );
391+ source .base_offset = PY_SSIZE_T_MAX - 1 ;
392+ if (check (_PyTok_SourceAppendLine (& source , "z\n" , 2 , 0 ) < 0 &&
393+ PyErr_ExceptionMatches (PyExc_MemoryError ),
394+ "accepted overflowing logical source offset" ) < 0 ) {
395+ goto error ;
396+ }
397+ PyErr_Clear ();
398+ if (check (source .len == 0 && source .nlines == 0 &&
399+ source .base_offset == PY_SSIZE_T_MAX - 1 ,
400+ "overflow changed retained source" ) < 0 ) {
401+ goto error ;
402+ }
299403 _PyTok_SourceClear (& source );
300404 Py_RETURN_NONE ;
301405
@@ -307,6 +411,7 @@ test_tokenizer_cursor(PyObject *Py_UNUSED(module),
307411static PyMethodDef test_methods [] = {
308412 {"test_tokenizer_source" , test_tokenizer_source , METH_NOARGS },
309413 {"test_tokenizer_cursor" , test_tokenizer_cursor , METH_NOARGS },
414+ {"test_tokenizer_source_discard" , test_tokenizer_source_discard , METH_NOARGS },
310415 {NULL },
311416};
312417
0 commit comments