@@ -250,3 +250,61 @@ def _expect_syntax(source, mode):
250250 raise AssertionError ("initial eval indentation was accepted" )
251251
252252assert eval (compile ("(\n 1\n )" , "<test>" , "eval" )) == 1
253+
254+ # Physical CR and CRLF are translated before lexing, even inside literals.
255+ # Keep explicit backslash-r escapes distinct from physical line endings.
256+ for ending in ("\r " , "\r \n " , "\n \r " , "\r \r \n " ):
257+ normalized = ending .replace ("\r \n " , "\n " ).replace ("\r " , "\n " )
258+ for prefix in ("" , "r" , "b" , "f" ):
259+ source = prefix + '"""a' + ending + 'b"""'
260+ expected = "a" + normalized + "b"
261+ if prefix == "b" :
262+ expected = expected .encode ()
263+ assert eval (source ) == expected
264+ assert eval (source .encode ()) == expected
265+ assert eval (compile (source , "<newlines>" , "eval" )) == expected
266+ tree = ast .parse (source , mode = "eval" )
267+ assert eval (compile (tree , "<newlines>" , "eval" )) == expected
268+ namespace = {}
269+ exec ("value = " + source , namespace )
270+ assert namespace ["value" ] == expected
271+ assert eval ('t"""a' + ending + 'b"""' ).strings == ("a" + normalized + "b" ,)
272+ namespace = {}
273+ exec ('def f():\r """a' + ending + ' b"""\r pass' , namespace )
274+ assert namespace ["f" ].__doc__ == "a" + normalized + "b"
275+ assert eval ('"a\\ ' + ending [:1 ] + 'b"' ) == "ab"
276+ assert eval (r'"a\rb"' ) == "a\r b"
277+ assert eval (r'r"a\rb"' ) == r"a\rb"
278+
279+ # Warnings and SyntaxError text must use the same source as the parser.
280+ import warnings
281+
282+ for source in ('"""a\r \\ q"""' , '"""a\r \\ q""" + )' , "1\r +" , '"a\r b"' ):
283+ results = []
284+ for text in (source , source .replace ("\r " , "\n " )):
285+ with warnings .catch_warnings (record = True ) as captured :
286+ warnings .simplefilter ("always" )
287+ try :
288+ eval (text )
289+ except SyntaxError as exc :
290+ result = (exc .msg , exc .lineno , exc .offset , exc .text )
291+ else :
292+ result = None
293+ results .append ((result , [(str (w .message ), w .lineno ) for w in captured ]))
294+ assert results [0 ] == results [1 ], (source , results )
295+
296+ # Audit hooks see the caller's bytes, not the parser's translated buffer.
297+ import sys
298+
299+ newline_audit_events = []
300+ newline_audit_source = '"""audit\r source"""'
301+
302+
303+ def record_newline_compile (event , args ):
304+ if event == "compile" and args [0 ] == newline_audit_source .encode ():
305+ newline_audit_events .append (args [0 ])
306+
307+
308+ sys .addaudithook (record_newline_compile )
309+ assert eval (newline_audit_source ) == "audit\n source"
310+ assert newline_audit_events == [newline_audit_source .encode ()]
0 commit comments