@@ -124,6 +124,71 @@ def test_incomplete_string_diagnostics(self, symbol, prefix):
124124 self .assertEqual (cm .exception .args , (
125125 'incomplete input' , ('<input>' , 2 , 9 , text , 2 , - 1 )))
126126
127+ @subTests ('symbol' , ('single' , 'exec' , 'eval' ))
128+ def test_unfinished_token_in_invalid_position (self , symbol ):
129+ for source in ('a b' , '(a b' , '1 """' , '(1 """' ,
130+ 'f"{lambda:\\ ' , 't"{lambda:\\ ' ):
131+ for compiler in COMPILERS :
132+ with self .subTest (source = source , compiler = compiler ):
133+ with self .assertRaises (SyntaxError ) as cm :
134+ compiler (source , '<input>' , symbol )
135+ self .assertNotIsInstance (cm .exception , _IncompleteInputError )
136+
137+ @subTests ('symbol' , ('single' , 'exec' , 'eval' ))
138+ def test_invalid_partial_string_diagnostic (self , symbol ):
139+ with self .assertRaisesRegex (SyntaxError , 'invalid syntax' ):
140+ Compile ()('a"""' , '<input>' , symbol )
141+
142+ @subTests ('symbol' , ('single' , 'exec' , 'eval' ))
143+ @subTests ('prefix' , ('' , 'r' , 'b' , 'f' , 't' , 'rf' , 'rt' ))
144+ def test_string_continuation (self , symbol , prefix ):
145+ source = prefix + '"first\\ '
146+ compile (source + '\n second"\n ' , '<input>' , symbol )
147+ self .assertIsNone (compile_command (source , symbol = symbol ))
148+ with self .assertRaises (_IncompleteInputError ):
149+ Compile ()(source , '<input>' , symbol )
150+
151+ @subTests ('symbol' , ('single' , 'exec' , 'eval' ))
152+ @subTests ('newline' , ('\n ' , '\r ' , '\r \n ' ))
153+ def test_continued_final_line (self , symbol , newline ):
154+ source = '1\\ ' + newline
155+ compile (source + '+ 2\n ' , '<input>' , symbol )
156+ self .assertIsNone (compile_command (source , symbol = symbol ))
157+ with self .assertRaises (_IncompleteInputError ):
158+ Compile ()(source , '<input>' , symbol )
159+ with self .assertRaises (SyntaxError ) as cm :
160+ Compile ()('1 + \\ ' + newline , '<input>' , symbol )
161+ # Appending LF to CR completes the CRLF line terminator.
162+ if newline != '\r ' :
163+ self .assertNotIsInstance (cm .exception , _IncompleteInputError )
164+
165+ @subTests ('symbol' , ('single' , 'exec' ))
166+ def test_eof_before_nested_body (self , symbol ):
167+ for source in ('if True:\n if True:' ,
168+ 'if True:\n def f():' ,
169+ 'match 1:\n case 1:' ):
170+ for tail in ('' , '\n ' , '\n \n ' ):
171+ with self .subTest (source = source , tail = tail ):
172+ compile (source + tail + '\n pass\n ' , '<input>' , symbol )
173+ self .assertIsNone (compile_command (source + tail , symbol = symbol ))
174+ with self .assertRaises (_IncompleteInputError ):
175+ Compile ()(source + tail , '<input>' , symbol )
176+
177+ def test_incomplete_suite_semantic_errors (self ):
178+ cases = (
179+ ('if True: break' , "'break' outside loop" ),
180+ ('if True: return 1' , "'return' outside function" ),
181+ ('def f(x, x):\n pass' , 'duplicate parameter' ),
182+ )
183+ for source , message in cases :
184+ for compiler in COMPILERS :
185+ with self .subTest (source = source , compiler = compiler ):
186+ with self .assertRaises (SyntaxError ) as cm :
187+ compiler (source , '<input>' , 'single' )
188+ self .assertNotIsInstance (cm .exception , _IncompleteInputError )
189+ if compiler in RAW_COMPILERS :
190+ self .assertIn (message , str (cm .exception ))
191+
127192 @subTests ('compiler' , COMPILERS )
128193 def test_incomplete (self , compiler ):
129194 ai = functools .partial (self .assertIncomplete , compiler = compiler )
0 commit comments