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

Issue:
Type inference fails with functions picked by a selector
If the return parameter of a function returns another function you can not immediate call the returned function.

Reproducible with this nice oneliner:
```elm
Html.text <| .greeting { greeting = \name -> "Hello " ++ name ++ "!" } "John Doe"
```

## Expected Behavior
There should be no error on this place after opening/editing the document.

## Current Behavior
The error with following text will be generated beginning on .greeting until the end of the line:
```
Type mismatch error.
Expected: `String`
Found: `String -> String`
```

## Possible Solution
The error disappears if you change the code to this:
```elm
Html.text <| (.greeting { greeting = \name -> "Hello " ++ name ++ "!" }) "John Doe"
```
But `elm-format` will remove the unnecessary braces and the error appears again.

## Steps to Reproduce (for bugs)
1. Create a new elm project with language server configured.
2. Put in the main function the code from the first code block of this issue message.
3. The error is now shown.
4. If you put `.greeting { greeting = \name -> "Hello " ++ name ++ "!" }` enclosed within braces the error disappers.
5. Sometimes if you save the document the error disappears. I noticed, if elm-format changes the file then the error appears again.

You can also setup the language server with the usage example code of the ellie link below.

## Context
In my use case i use this for translations. If i want to format parameters in my translation i define it like followed:
```elm
english : Translations
english =
    { greeting = \name -> "Hello " ++ name ++ "!" }
```

Then i call a function which picks the right `Translation` record via a language parameter:
```
Html.text <| get English .greeting "John Doe"
```

The get function takes the `English` variant and applies `.greeting` on the `english` record. The get function returns in this case another function which takes a `String` which is immediately applied with the `"John Doe"` string literal. If the `get` call is not enclosed within braces then the language server generates an error.

You can find the sample code for my use case here: https://ellie-app.com/c7CPPGF2rQ5a1

## Your Environment
* Version used: 2.0.0
* Editor used: Visual Studio Code
```
Version: 1.52.1 (system setup)
Commit: ea3859d4ba2f3e577a159bc91e3074c5d85c0523
Datum: 2020-12-16T16:34:46.910Z
Electron: 9.3.5
Chrome: 83.0.4103.122
Node.js: 12.14.1
V8: 8.3.110.13-electron.0
Betriebssystem: Windows_NT x64 10.0.18363
```
* Environment name and version (e.g. node.js 5.4): node v13.10.1
* Operating System and version: Windows 10 Version 1909 (Build 18363.1316)



Pull request description:
Fix type inference of partially applied functions
Another silly one. Fixes #454.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 81fe3d4..04cf5d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,5 @@
 # Change Log
 
-## 2.0.1
-
-- Remove `file` entry from symbolproviders (Outline, Go to symbol and breadcrumbs)
-- Don't show files from dependencies in WorkspaceSymbolProvider (Go to symbol in workspace)
-
 ## 2.0.0
 
 ### Features
diff --git a/src/providers/hoverProvider.ts b/src/providers/hoverProvider.ts
index 5de50c3..d3d8c1b 100644
--- a/src/providers/hoverProvider.ts
+++ b/src/providers/hoverProvider.ts
@@ -46,7 +46,7 @@ export class HoverProvider {
         params.position,
       );
 
-      let definitionNode = checker.findDefinition(
+      const definitionNode = checker.findDefinition(
         nodeAtPosition,
         treeContainer,
       );
@@ -56,10 +56,7 @@ export class HoverProvider {
           definitionNode.node.type === "function_declaration_left" &&
           definitionNode.node.parent
         ) {
-          definitionNode = {
-            ...definitionNode,
-            node: definitionNode.node.parent,
-          };
+          definitionNode.node = definitionNode.node.parent;
         }
 
         const typeString = checker.typeToString(
diff --git a/src/providers/linkedEditingRangesProvider.ts b/src/providers/linkedEditingRangesProvider.ts
index a5b9ed4..69d16dd 100644
--- a/src/providers/linkedEditingRangesProvider.ts
+++ b/src/providers/linkedEditingRangesProvider.ts
@@ -12,7 +12,7 @@ import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { TreeUtils } from "../util/treeUtils";
 import { ILinkedEditingRangeParams } from "./paramsExtensions";
 
-export class LinkedEditingRangesProvider {
+export class LinkedEditiRangesProvider {
   private connection: Connection;
 
   constructor() {
@@ -29,31 +29,31 @@ export class LinkedEditingRangesProvider {
     params: ILinkedEditingRangeParams,
   ): LinkedEditingRanges => {
     const ranges: Range[] = [];
-    const nodeAtPosition = TreeUtils.getNamedDescendantForPosition(
+    const nodeAtPostion = TreeUtils.getNamedDescendantForPosition(
       params.sourceFile.tree.rootNode,
       params.position,
     );
 
     const range: Range = {
       start: PositionUtil.FROM_TS_POSITION(
-        nodeAtPosition.startPosition,
+        nodeAtPostion.startPosition,
       ).toVSPosition(),
       end: PositionUtil.FROM_TS_POSITION(
-        nodeAtPosition.endPosition,
+        nodeAtPostion.endPosition,
       ).toVSPosition(),
     };
 
     if (
-      nodeAtPosition.parent?.type === "function_declaration_left" &&
-      TreeUtils.getTypeAnnotation(nodeAtPosition.parent.parent ?? undefined)
+      nodeAtPostion.parent?.type === "function_declaration_left" &&
+      TreeUtils.getTypeAnnotation(nodeAtPostion.parent.parent ?? undefined)
     ) {
       ranges.push(this.addLinesToRange(range, -1));
       ranges.push(range);
     }
 
     if (
-      nodeAtPosition.parent?.type === "type_annotation" &&
-      nodeAtPosition.type === "lower_case_identifier"
+      nodeAtPostion.parent?.type === "type_annotation" &&
+      nodeAtPostion.type === "lower_case_identifier"
     ) {
       ranges.push(range);
       ranges.push(this.addLinesToRange(range, 1));
diff --git a/src/providers/workspaceSymbolProvider.ts b/src/providers/workspaceSymbolProvider.ts
index c51e400..141b80c 100644
--- a/src/providers/workspaceSymbolProvider.ts
+++ b/src/providers/workspaceSymbolProvider.ts
@@ -29,9 +29,6 @@ export class WorkspaceSymbolProvider {
 
     this.elmWorkspaces.forEach((elmWorkspace) => {
       elmWorkspace.getForest().treeMap.forEach((tree) => {
-        if (!tree.writeable) {
-          return;
-        }
         const traverse: (node: SyntaxNode) => void = (
           node: SyntaxNode,
         ): void => {
diff --git a/src/server.ts b/src/server.ts
index b9a5c97..82ced2c 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -21,7 +21,7 @@ import {
   ElmMakeDiagnostics,
   FoldingRangeProvider,
   HoverProvider,
-  LinkedEditingRangesProvider,
+  LinkedEditiRangesProvider,
   ReferencesProvider,
   RenameProvider,
   SelectionRangeProvider,
@@ -161,7 +161,7 @@ export class Server implements ILanguageServer {
     new SelectionRangeProvider();
     new RenameProvider();
     new FileEventsHandler();
-    new LinkedEditingRangesProvider();
+    new LinkedEditiRangesProvider();
   }
 
   private getElmJsonFolder(uri: string): URI {
diff --git a/src/util/symbolTranslator.ts b/src/util/symbolTranslator.ts
index f8b8586..008bc66 100644
--- a/src/util/symbolTranslator.ts
+++ b/src/util/symbolTranslator.ts
@@ -12,6 +12,8 @@ export class SymbolInformationTranslator {
     node: SyntaxNode,
   ): SymbolInformation | undefined {
     switch (node.type) {
+      case "file":
+        return this.createSymbolInformation("file", node, SymbolKind.File, uri);
       case "value_declaration":
         {
           const functionName = node.firstChild?.firstChild?.text;
diff --git a/src/util/types/typeChecker.ts b/src/util/types/typeChecker.ts
index 9727891..ecfea41 100644
--- a/src/util/types/typeChecker.ts
+++ b/src/util/types/typeChecker.ts
@@ -667,7 +667,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
           uri,
           nodeType: "Operator",
         };
-        operatorsCache.set(nodeText, Object.freeze(result));
+        operatorsCache.set(nodeText, result);
         return result;
       } else {
         const definitionFromOtherFile = findImportOfType(
@@ -677,7 +677,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         );
 
         if (definitionFromOtherFile) {
-          operatorsCache.set(nodeText, Object.freeze(definitionFromOtherFile));
+          operatorsCache.set(nodeText, definitionFromOtherFile);
           return definitionFromOtherFile;
         }
       }
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index eaabdb2..68a6855 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -1191,7 +1191,7 @@ export class InferenceScope {
         return argCountError(e, e, e.args.length, targetType.params.length);
       }
 
-      for (let i = targetType.params.length; i < e.args.length - 1; i++) {
+      for (let i = targetType.params.length; i < e.args.length; i++) {
         if (appliedType.nodeType !== "Function") {
           return argCountError(e.target, e.args[i], 1, 0);
         }


Test patch:
diff --git a/test/typeInference.test.ts b/test/typeInference.test.ts
index f5a9ad4..dc38251 100644
--- a/test/typeInference.test.ts
+++ b/test/typeInference.test.ts
@@ -636,4 +636,20 @@ func a =
 `;
     await testTypeInference(basicsSources + source, "number -> number");
   });
+
+  test("partially applied functions", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+text : Int -> Int
+text int =
+    int + 1
+
+func =
+--^
+  text (.greeting { greeting = \\name -> 1 + name + 3 } 4)
+`;
+    await testTypeInference(basicsSources + source, "Int");
+  });
 });