Repository: kcl-lang/kcl (language: rust)

Issue:
[LSP] Recursion in LSP causing memory leak and crash
## Bug Report

### 1. Minimal reproduce step (Required)

Type out the following code with the LSP watching

```
schema Options:
    sub?: Sub

schema Sub:
    name: str

opts = Options{}

x = {
    a: True

    if opts.sub:
}
```

The issue appears once you type the `:` (colon) in the if statement.

### 2. What did you expect to see? (Required)

A single log from the LSP and stable memory usage.

### 3. What did you see instead (Required)

Continuous repeated logs from the LSP and increasing memory usage resulting in a crash.

Logs such as `db state: In compiling, retry` and `Process changed file, open Ok(...)`.

### 4. What is your KCL components version? (Required)

version `0.11.0` on both the CLI and the language server.



Discussion / hints:

Fix patch:
diff --git a/kclvm/parser/src/parser/expr.rs b/kclvm/parser/src/parser/expr.rs
index 45f886ff7..f3a501e79 100644
--- a/kclvm/parser/src/parser/expr.rs
+++ b/kclvm/parser/src/parser/expr.rs
@@ -1718,6 +1718,36 @@ impl<'a> Parser<'a> {
             }
         };
 
+        if !need_skip_newlines {
+            // Only parse one inline key-value pair.
+            parse_body_item(self, &mut body, need_skip_newlines);
+        } else {
+            while parse_body_item(self, &mut body, need_skip_newlines) {
+                if self.token.kind == TokenKind::Eof {
+                    self.bump();
+                    break;
+                }
+                // bump optional comma at the endline.
+                if let TokenKind::Comma = self.token.kind {
+                    self.bump();
+                }
+                self.skip_newlines();
+            }
+        }
+
+        return if need_skip_newlines {
+            self.skip_newlines();
+            Box::new(Node::node(
+                body,
+                self.sess.struct_token_loc(token, self.prev_token),
+            ))
+        } else {
+            Box::new(Node::node(
+                body,
+                self.sess.struct_token_loc(token, self.prev_token),
+            ))
+        };
+
         fn parse_body_item(
             this: &mut Parser,
             body: &mut ConfigIfEntryExpr,
@@ -1844,32 +1874,6 @@ impl<'a> Parser<'a> {
             this.drop(marker);
             true
         }
-
-        if !need_skip_newlines {
-            // Only parse one inline key-value pair.
-            parse_body_item(self, &mut body, need_skip_newlines);
-        } else {
-            while parse_body_item(self, &mut body, need_skip_newlines) {
-                // bump optional comma at the endline.
-                if let TokenKind::Comma = self.token.kind {
-                    self.bump();
-                }
-                self.skip_newlines();
-            }
-        }
-
-        if need_skip_newlines {
-            self.skip_newlines();
-            Box::new(Node::node(
-                body,
-                self.sess.struct_token_loc(token, self.prev_token),
-            ))
-        } else {
-            Box::new(Node::node(
-                body,
-                self.sess.struct_token_loc(token, self.prev_token),
-            ))
-        }
     }
 
     /// Syntax:


Test patch:
diff --git a/kclvm/parser/src/tests/error_recovery.rs b/kclvm/parser/src/tests/error_recovery.rs
index 0f0c9052c..6a1f70823 100644
--- a/kclvm/parser/src/tests/error_recovery.rs
+++ b/kclvm/parser/src/tests/error_recovery.rs
@@ -423,3 +423,8 @@ parse_module_snapshot! { fn_ty_annotation_recovery_21, r#"a:(str|int, int) -> i"
 parse_module_snapshot! { fn_ty_annotation_recovery_22, r#"a:(str|int, int|"#}
 parse_module_snapshot! { fn_ty_annotation_recovery_23, r#"a:(str|int, int|) ->"#}
 parse_module_snapshot! { import_recovery_0, r#"import json as j.a"#}
+
+parse_module_snapshot! { parse_body_item, r#"
+x = {
+    if True
+}"#}
diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__parse_body_item.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__parse_body_item.snap
new file mode 100644
index 000000000..8c7f02971
--- /dev/null
+++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__parse_body_item.snap
@@ -0,0 +1,93 @@
+---
+source: parser/src/tests/error_recovery.rs
+expression: "$crate :: tests :: parsing_module_string(r#\"\nx = {\n    if True\n}\"#)"
+---
+Module {
+    filename: "",
+    doc: None,
+    body: [
+        Node {
+            node: Assign(
+                AssignStmt {
+                    targets: [
+                        Node {
+                            node: Target {
+                                name: Node {
+                                    node: "x",
+                                    filename: "",
+                                    line: 2,
+                                    column: 0,
+                                    end_line: 2,
+                                    end_column: 1,
+                                },
+                                paths: [],
+                                pkgpath: "",
+                            },
+                            filename: "",
+                            line: 2,
+                            column: 0,
+                            end_line: 2,
+                            end_column: 1,
+                        },
+                    ],
+                    value: Node {
+                        node: Config(
+                            ConfigExpr {
+                                items: [
+                                    Node {
+                                        node: ConfigEntry {
+                                            key: None,
+                                            value: Node {
+                                                node: ConfigIfEntry(
+                                                    ConfigIfEntryExpr {
+                                                        if_cond: Node {
+                                                            node: NameConstantLit(
+                                                                NameConstantLit {
+                                                                    value: True,
+                                                                },
+                                                            ),
+                                                            filename: "",
+                                                            line: 3,
+                                                            column: 7,
+                                                            end_line: 3,
+                                                            end_column: 11,
+                                                        },
+                                                        items: [],
+                                                        orelse: None,
+                                                    },
+                                                ),
+                                                filename: "",
+                                                line: 3,
+                                                column: 4,
+                                                end_line: 4,
+                                                end_column: 0,
+                                            },
+                                            operation: Union,
+                                        },
+                                        filename: "",
+                                        line: 3,
+                                        column: 4,
+                                        end_line: 4,
+                                        end_column: 0,
+                                    },
+                                ],
+                            },
+                        ),
+                        filename: "",
+                        line: 2,
+                        column: 4,
+                        end_line: 4,
+                        end_column: 1,
+                    },
+                    ty: None,
+                },
+            ),
+            filename: "",
+            line: 2,
+            column: 0,
+            end_line: 4,
+            end_column: 1,
+        },
+    ],
+    comments: [],
+}