Repository: RustPython/RustPython (language: rust)

Issue:
IndentationError works differently with cpython in interective shell.
cpython returns `IndentationError` when it gets ‘\n’ after statements like `for`, `if`
-  cpython
```
>>> for i in range(1,4):
... 
  File "<stdin>", line 2
    
    ^
IndentationError: expected an indented block
```
but rustpython keeps waiting until some string come in.
- rustpython
```
>>>>> for i in range(1,4):
..... 
..... 
..... d
..... 
IndentationError: expected an indented block at line 4 column 1
```


Discussion / hints:
@kth496 do you have advices for this issue?
@youknowone There is nothing to comment yet 😭 I'm looking at it with Hyeonji-Ryu to solve the issue! :)

Fix patch:
diff --git a/compiler/parser/src/error.rs b/compiler/parser/src/error.rs
index e1e6b2b812..b6611c2963 100644
--- a/compiler/parser/src/error.rs
+++ b/compiler/parser/src/error.rs
@@ -205,11 +205,23 @@ pub(crate) fn parse_error_from_lalrpop(
                 source_path,
             }
         }
-        LalrpopError::UnrecognizedEOF { location, .. } => ParseError {
-            error: ParseErrorType::Eof,
-            location,
-            source_path,
-        },
+        LalrpopError::UnrecognizedEOF { location, expected } => {
+            // This could be an initial indentation error that we should ignore
+            let indent_error = expected == ["Indent"];
+            if indent_error {
+                ParseError {
+                    error: ParseErrorType::Lexical(LexicalErrorType::IndentationError),
+                    location,
+                    source_path,
+                }
+            } else {
+                ParseError {
+                    error: ParseErrorType::Eof,
+                    location,
+                    source_path,
+                }
+            }
+        }
     }
 }
 
diff --git a/src/shell.rs b/src/shell.rs
index 5a9d887b4b..87680bf302 100644
--- a/src/shell.rs
+++ b/src/shell.rs
@@ -15,12 +15,28 @@ enum ShellExecResult {
     Continue,
 }
 
-fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResult {
+fn shell_exec(
+    vm: &VirtualMachine,
+    source: &str,
+    scope: Scope,
+    last_row: &mut usize,
+    empty_line_given: bool,
+    continuing: bool,
+) -> ShellExecResult {
     match vm.compile(source, compiler::Mode::Single, "<stdin>".to_owned()) {
-        Ok(code) => match vm.run_code_obj(code, scope) {
-            Ok(_val) => ShellExecResult::Ok,
-            Err(err) => ShellExecResult::PyErr(err),
-        },
+        Ok(code) => {
+            if empty_line_given || !continuing {
+                // We want to execute the full code
+                *last_row = 0;
+                match vm.run_code_obj(code, scope) {
+                    Ok(_val) => ShellExecResult::Ok,
+                    Err(err) => ShellExecResult::PyErr(err),
+                }
+            } else {
+                // We can just return an ok result
+                ShellExecResult::Ok
+            }
+        }
         Err(CompileError {
             body:
                 CompileErrorBody {
@@ -37,7 +53,31 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
                 },
             ..
         }) => ShellExecResult::Continue,
-        Err(err) => ShellExecResult::PyErr(vm.new_syntax_error(&err)),
+        Err(err) => {
+            // Indent error or something else?
+            let indent_error = match err.body.error {
+                CompileErrorType::Parse(ref p) => p.is_indentation_error(),
+                _ => false,
+            };
+
+            if indent_error && !empty_line_given {
+                // The input line is not empty and it threw an indentation error
+                let l = err.body.location;
+
+                // This is how we can mask unnecesary errors
+                if l.row() > *last_row {
+                    *last_row = l.row();
+                    ShellExecResult::Continue
+                } else {
+                    *last_row = 0;
+                    ShellExecResult::PyErr(vm.new_syntax_error(&err))
+                }
+            } else {
+                // Throw the error for all other cases
+                *last_row = 0;
+                ShellExecResult::PyErr(vm.new_syntax_error(&err))
+            }
+        }
     }
 }
 
@@ -60,6 +100,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
     }
 
     let mut continuing = false;
+    let mut last_row: usize = 0;
 
     loop {
         let prompt_name = if continuing { "ps2" } else { "ps1" };
@@ -78,7 +119,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
 
                 repl.add_history_entry(line.trim_end()).unwrap();
 
-                let stop_continuing = line.is_empty();
+                let empty_line_given = line.is_empty();
 
                 if full_input.is_empty() {
                     full_input = line;
@@ -87,30 +128,47 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
                 }
                 full_input.push('\n');
 
-                if continuing {
-                    if stop_continuing {
-                        continuing = false;
-                    } else {
-                        continue;
-                    }
-                }
-
-                match shell_exec(vm, &full_input, scope.clone()) {
+                match shell_exec(
+                    vm,
+                    &full_input,
+                    scope.clone(),
+                    &mut last_row,
+                    empty_line_given,
+                    continuing,
+                ) {
                     ShellExecResult::Ok => {
-                        full_input.clear();
-                        Ok(())
+                        if continuing {
+                            if empty_line_given {
+                                // We should be exiting continue mode
+                                continuing = false;
+                                full_input.clear();
+                                Ok(())
+                            } else {
+                                // We should stay in continue mode
+                                continuing = true;
+                                Ok(())
+                            }
+                        } else {
+                            // We aren't in continue mode so proceed normally
+                            last_row = 0;
+                            continuing = false;
+                            full_input.clear();
+                            Ok(())
+                        }
                     }
                     ShellExecResult::Continue => {
                         continuing = true;
                         Ok(())
                     }
                     ShellExecResult::PyErr(err) => {
+                        continuing = false;
                         full_input.clear();
                         Err(err)
                     }
                 }
             }
             ReadlineResult::Interrupt => {
+                last_row = 0;
                 continuing = false;
                 full_input.clear();
                 let keyboard_interrupt =


Test patch: