Repository: elm-tooling/elm-language-server (ts)

Issue:
Handle recursion like the compiler
We need to find a way to handle recursion like the compiler does. 

This code is valid in the compiler:
```elm
func : Int -> String
func =
    let
        go n =
            if n == 1 then
                "text"

            else
                func n ++ "more"
    in
    go
```

Our type inference system doesn't allow recursion for functions with no parameters, but it should actually look at the return type of the function. The problem is that we don't know the return type until we recursively check itself.  

 https://github.com/elm-tooling/elm-language-server/blob/40bac9ab7aea797a01a7fe5c0e25bce93506769e/src/util/types/typeInference.ts#L654

The solution seems to be non-trivial, so I will be studying the compiler to understand how we can achieve this. 

Pull request description:
Fix recursion error when used in let function
Fixes #425.

Patch:
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index f9fd06c..f18630f 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -852,6 +852,7 @@ export class InferenceScope {
     const result = this.inferChild(
       (inference) => inference.inferDeclaration(declaration, false),
       new Set(activeScopes.values()),
+      /* recursionAllowed */ declaration.params.length > 0,
     );
 
     this.resolvedDeclarations.set(declaration, result.type);


Test patch:
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index b023e56..57c8664 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -64,6 +64,7 @@ describe("test elm diagnostics", () => {
       message: IDiagnosticMessage;
       args: (string | number)[];
     }[],
+    disableSuggestionDiagnostics?: boolean,
   ) {
     await treeParser.init();
 
@@ -88,7 +89,10 @@ describe("test elm diagnostics", () => {
       if (!treeContainer.uri.includes("Basic")) {
         diagnostics.push(...program.getSyntacticDiagnostics(treeContainer));
         diagnostics.push(...program.getSemanticDiagnostics(treeContainer));
-        diagnostics.push(...program.getSuggestionDiagnostics(treeContainer));
+
+        if (!disableSuggestionDiagnostics) {
+          diagnostics.push(...program.getSuggestionDiagnostics(treeContainer));
+        }
       }
     });
 
@@ -283,4 +287,58 @@ withFilter filter chart =
 
     await testTypeInference(basicsSources + source, []);
   });
+
+  test("recursion inside a let function with params", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func : Int -> Int
+func =
+    let
+        go n =
+            if n == 1 then
+                2
+
+            else
+                func n + 1
+    in
+    go
+
+
+func2 =
+    let
+        go n =
+            if n == 1 then
+                "text"
+
+            else
+                func2 n 
+    in
+    go
+  `;
+    await testTypeInference(basicsSources + source, [], true);
+
+    const source2 = `
+    --@ Test.elm
+module Test exposing (..)
+
+func =
+     --^
+    let
+        go =
+            if False then
+                "text"
+
+            else
+                func
+    in
+    go
+      `;
+    await testTypeInference(
+      basicsSources + source2,
+      [{ message: Diagnostics.InfiniteRecursion, args: [] }],
+      true,
+    );
+  });
 });