@@ -50,9 +50,13 @@ pub enum CompileError {
5050
5151impl CompileError {
5252 #[ must_use]
53- pub fn from_ruff_parse_error ( error : parser:: ParseError , source_file : & SourceFile ) -> Self {
53+ pub fn from_ruff_parse_error (
54+ error : parser:: ParseError ,
55+ source_file : & SourceFile ,
56+ mode : Mode ,
57+ ) -> Self {
5458 let raw_location = error. location ;
55- let diagnostic = match cpython_parse_diagnostic_override ( & error, source_file) {
59+ let diagnostic = match cpython_parse_diagnostic_override ( & error, source_file, mode ) {
5660 Some ( diagnostic) => diagnostic,
5761 None => default_parse_diagnostic ( error, source_file) ,
5862 } ;
@@ -129,6 +133,13 @@ fn source_location(source_file: &SourceFile, offset: TextSize) -> SourceLocation
129133 . source_location ( offset, PositionEncoding :: Utf8 )
130134}
131135
136+ // Call only with UTF-8 character boundaries for Python-facing offsets.
137+ fn source_location_in_code_points ( source_file : & SourceFile , offset : TextSize ) -> SourceLocation {
138+ source_file
139+ . to_source_code ( )
140+ . source_location ( offset, PositionEncoding :: Utf32 )
141+ }
142+
132143fn source_locations (
133144 source_file : & SourceFile ,
134145 start : TextSize ,
@@ -175,6 +186,21 @@ impl NormalizedParseDiagnostic {
175186 )
176187 }
177188
189+ fn other_in_code_points (
190+ source_file : & SourceFile ,
191+ message : String ,
192+ start : usize ,
193+ end : usize ,
194+ ) -> Self {
195+ let start = TextSize :: new ( start as u32 ) ;
196+ let end = TextSize :: new ( end as u32 ) ;
197+ Self :: new (
198+ parser:: ParseErrorType :: OtherError ( message) ,
199+ source_location_in_code_points ( source_file, start) ,
200+ source_location_in_code_points ( source_file, end) ,
201+ )
202+ }
203+
178204 const fn with_unclosed_bracket ( mut self , is_unclosed_bracket : bool ) -> Self {
179205 self . is_unclosed_bracket = is_unclosed_bracket;
180206 self
@@ -184,6 +210,7 @@ impl NormalizedParseDiagnostic {
184210fn cpython_parse_diagnostic_override (
185211 error : & parser:: ParseError ,
186212 source_file : & SourceFile ,
213+ mode : Mode ,
187214) -> Option < NormalizedParseDiagnostic > {
188215 let source_text = source_file. source_text ( ) ;
189216
@@ -223,6 +250,18 @@ fn cpython_parse_diagnostic_override(
223250 & error. error,
224251 parser:: ParseErrorType :: Lexical ( parser:: LexicalErrorType :: LineContinuationError )
225252 ) {
253+ // Only a backslash at the end of the source is an EOF error.
254+ let terminal_backslash = source_text. len ( ) . checked_sub ( 1 ) ;
255+ if !matches ! ( mode, Mode :: Eval )
256+ && terminal_backslash == Some ( error. location . start ( ) . to_usize ( ) )
257+ {
258+ let loc = source_line_end_location ( source_file, error. location . start ( ) ) ;
259+ return Some ( NormalizedParseDiagnostic :: new (
260+ parser:: ParseErrorType :: OtherError ( "unexpected EOF while parsing" . to_owned ( ) ) ,
261+ loc,
262+ loc,
263+ ) ) ;
264+ }
226265 let loc = source_location ( source_file, error. location . start ( ) + TextSize :: from ( 1 ) ) ;
227266 return Some ( NormalizedParseDiagnostic :: new (
228267 error. error . clone ( ) ,
@@ -231,7 +270,15 @@ fn cpython_parse_diagnostic_override(
231270 ) ) ;
232271 }
233272
234- source_error ! ( unterminated_string_error( source_text) ) ;
273+ if let Some ( ( message, start, end) ) = unterminated_string_error ( source_text) {
274+ // The scanner reports quote positions, which are UTF-8 character boundaries.
275+ return Some ( NormalizedParseDiagnostic :: other_in_code_points (
276+ source_file,
277+ message,
278+ start,
279+ end,
280+ ) ) ;
281+ }
235282 source_error ! ( expected_indented_block_error( error, source_text) ) ;
236283
237284 if matches ! (
@@ -5176,7 +5223,7 @@ fn _compile_with_syntax_warning_handler<'a>(
51765223 } ;
51775224 let parser_options = parser:: ParseOptions :: from ( parser_mode) ;
51785225 let parsed = parser:: parse ( source_file. source_text ( ) , parser_options)
5179- . map_err ( |err| CompileError :: from_ruff_parse_error ( err, & source_file) ) ?;
5226+ . map_err ( |err| CompileError :: from_ruff_parse_error ( err, & source_file, mode ) ) ?;
51805227 if opts. dont_imply_dedent
51815228 && matches ! ( mode, Mode :: Single )
51825229 && let Some ( error) = dont_imply_dedent_source_error ( & source_file)
@@ -5235,7 +5282,7 @@ pub fn _compile_symtable(
52355282 let res = match mode {
52365283 Mode :: Exec | Mode :: Single | Mode :: BlockExpr => {
52375284 let ast = ruff_python_parser:: parse_module ( source_file. source_text ( ) )
5238- . map_err ( |e| CompileError :: from_ruff_parse_error ( e, & source_file) ) ?;
5285+ . map_err ( |e| CompileError :: from_ruff_parse_error ( e, & source_file, mode ) ) ?;
52395286 if let Some ( error) =
52405287 post_parse_source_error ( & source_file, ast. tokens ( ) , & CompileOpts :: default ( ) )
52415288 {
@@ -5254,7 +5301,7 @@ pub fn _compile_symtable(
52545301 source_file. source_text ( ) ,
52555302 parser:: Mode :: Expression . into ( ) ,
52565303 )
5257- . map_err ( |e| CompileError :: from_ruff_parse_error ( e, & source_file) ) ?;
5304+ . map_err ( |e| CompileError :: from_ruff_parse_error ( e, & source_file, mode ) ) ?;
52585305 if let Some ( error) =
52595306 post_parse_source_error ( & source_file, ast. tokens ( ) , & CompileOpts :: default ( ) )
52605307 {
0 commit comments