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

Issue:
Type inference incorrect when pattern matching lists of lists
When pattern-matching on a list of lists of data, some patterns seem to confuse the type-inference system. See this example:
```elm
test : List (List Float) -> Float
test listList =
    case listList of
        [ a, b ] :: _ ->
            let
                product : Float
                product =
                    a * b
            in
            product

        _ ->
            0
```

## Expected Behavior
The Elm compiler is happy and the code works as intended. The elm-language-server should be fine with it, too!

## Current Behavior
Instead, it reports a type mismatch on both `a` and `b`:
```
[Elm] [E] Type mismatch error.
Expected: `number`
Found: `List Float`
```

## Possible Solution
No idea, sorry!

## Steps to Reproduce (for bugs)
The code above compiles fine on Ellie. Simply pasting it in your editor (and NOT using the Elm compiler report) should be enough.

## Context
N/A

## Your Environment
* Version used: 2.0.1
* Editor used: neovim + CoC
* Environment name and version: nodejs v10.23.2
* Operating System and version: Ubuntu 20.04.1

Pull request:
Fix inference for cons patterns with lists
Fixes #513. 


Patch:
diff --git a/src/providers/codeAction/addMissingRecordFieldCodeAction.ts b/src/providers/codeAction/addMissingRecordFieldCodeAction.ts
deleted file mode 100644
index 75d3ac8..0000000
--- a/src/providers/codeAction/addMissingRecordFieldCodeAction.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { CodeAction, Range, TextEdit } from "vscode-languageserver";
-import { SyntaxNode } from "web-tree-sitter";
-import { CodeActionProvider } from "..";
-import { ITreeContainer } from "../../forest";
-import { getSpaces } from "../../util/refactorEditUtils";
-import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
-import { TypeChecker } from "../../util/types/typeChecker";
-import { Type } from "../../util/types/typeInference";
-import { ICodeActionParams } from "../paramsExtensions";
-
-const errorCodes = [Diagnostics.RecordField.code];
-const fixId = "add_missing_record_field";
-
-CodeActionProvider.registerCodeAction({
-  errorCodes,
-  fixId,
-  getCodeActions: (params: ICodeActionParams) => {
-    const edits = getEdits(params, params.range);
-
-    if (Object.values(edits).some((edit) => edit.length > 0)) {
-      const single = Object.values(edits).every((edit) => edit.length === 1);
-
-      return [
-        CodeActionProvider.getCodeAction(
-          params,
-          `Create missing record ${single ? "field" : "fields"}`,
-          edits,
-        ),
-      ];
-    }
-
-    return [];
-  },
-  getFixAllCodeAction: (params: ICodeActionParams): CodeAction | undefined => {
-    return CodeActionProvider.getFixAllCodeAction(
-      "Create all missing record fields",
-      params,
-      errorCodes,
-      fixId,
-      () => {
-        // Not used
-      },
-      (changes, diagnostic) => {
-        mergeChanges(changes, getEdits(params, diagnostic.range));
-      },
-    );
-  },
-});
-
-function getEdits(
-  params: ICodeActionParams,
-  range: Range,
-): { [uri: string]: TextEdit[] } {
-  const nodeAtPosition = TreeUtils.getNamedDescendantForRange(
-    params.sourceFile,
-    range,
-  );
-
-  const nodeParent = nodeAtPosition.parent;
-  if (
-    nodeAtPosition.type === "lower_case_identifier" &&
-    (nodeParent?.type === "field_access_expr" || nodeParent?.type === "field")
-  ) {
-    const checker = params.program.getTypeChecker();
-    const fieldName = nodeAtPosition.text;
-
-    let target =
-      nodeParent.type === "field_access_expr"
-        ? nodeParent?.childForFieldName("target")
-        : nodeParent.type === "field"
-        ? nodeParent.parent
-        : nodeAtPosition;
-
-    // Adjust for parenthesis expr. Will need to change when we handle it better in inference
-    if (target?.type === "parenthesized_expr") {
-      target = target.namedChildren[1];
-    }
-
-    if (target) {
-      const expectedType = checker.findType(
-        nodeParent.type === "field_access_expr" ? nodeParent : nodeAtPosition,
-      );
-
-      return createFields(
-        [[fieldName, expectedType]],
-        target,
-        checker,
-        params.sourceFile,
-      );
-    }
-  }
-
-  return {};
-}
-
-function createFields(
-  fields: [string, Type][],
-  targetRecord: SyntaxNode,
-  checker: TypeChecker,
-  sourceFile: ITreeContainer,
-): { [uri: string]: TextEdit[] } {
-  const type = checker.findType(targetRecord);
-
-  if (type.nodeType === "Record") {
-    const fieldNames = Object.keys(type.fields);
-    const lastFieldRef = TreeUtils.findFieldReference(
-      type,
-      fieldNames[fieldNames.length - 1],
-    );
-
-    if (lastFieldRef) {
-      const useNewLine =
-        lastFieldRef.node.parent?.startPosition.row !==
-        lastFieldRef.node.parent?.endPosition.row;
-      const indendation = getSpaces(lastFieldRef.node.startPosition.column - 2);
-
-      const edits = fields.map(([fieldName, fieldType]) => {
-        const typeString: string = checker.typeToString(fieldType, sourceFile);
-
-        return TextEdit.insert(
-          {
-            line: lastFieldRef?.node.endPosition.row,
-            character: lastFieldRef?.node.endPosition.column,
-          },
-          `${
-            useNewLine ? "\n" + indendation : ""
-          }, ${fieldName} : ${typeString}`,
-        );
-      });
-
-      return { [lastFieldRef.uri]: edits };
-    }
-  }
-
-  return {};
-}
-
-function mergeChanges(
-  a: { [uri: string]: TextEdit[] },
-  b: { [uri: string]: TextEdit[] },
-): void {
-  Object.entries(b).forEach(([uri, edits]) => {
-    if (a[uri]) {
-      a[uri].push(...edits);
-    } else {
-      a[uri] = edits;
-    }
-  });
-}
diff --git a/src/providers/codeAction/index.ts b/src/providers/codeAction/index.ts
index 22fa282..6270240 100644
--- a/src/providers/codeAction/index.ts
+++ b/src/providers/codeAction/index.ts
@@ -5,4 +5,3 @@ import "./extractFunctionCodeAction";
 import "./exposeUnexposeCodeAction";
 import "./moveFunctionCodeAction";
 import "./installPackageCodeAction";
-import "./addMissingRecordFieldCodeAction";
diff --git a/src/providers/codeActionProvider.ts b/src/providers/codeActionProvider.ts
index 134757c..ccf3542 100644
--- a/src/providers/codeActionProvider.ts
+++ b/src/providers/codeActionProvider.ts
@@ -161,12 +161,10 @@ export class CodeActionProvider {
   public static getCodeAction(
     params: ICodeActionParams,
     title: string,
-    edits: TextEdit[] | { [uri: string]: TextEdit[] },
+    edits: TextEdit[],
     command?: Command,
   ): CodeAction {
-    const changes = Array.isArray(edits)
-      ? { [params.sourceFile.uri]: edits }
-      : edits;
+    const changes = { [params.sourceFile.uri]: edits };
     return {
       title,
       kind: CodeActionKind.QuickFix,
@@ -182,26 +180,16 @@ export class CodeActionProvider {
     errorCodes: string[],
     fixId: string,
     callback: (edits: TextEdit[], diagnostic: LspDiagnostic) => void,
-    callbackChanges?: (
-      edits: { [uri: string]: TextEdit[] },
-      diagnostic: LspDiagnostic,
-    ) => void,
   ): CodeAction {
     const edits: TextEdit[] = [];
-    const changes = callbackChanges
-      ? {}
-      : {
-          [params.sourceFile.uri]: edits,
-        };
+    const changes = {
+      [params.sourceFile.uri]: edits,
+    };
 
     const diagnostics: LspDiagnostic[] = [];
     CodeActionProvider.forEachDiagnostic(params, errorCodes, (diagnostic) => {
       diagnostics.push(diagnostic);
-      if (callbackChanges) {
-        callbackChanges(changes, diagnostic);
-      } else {
-        callback(edits, diagnostic);
-      }
+      callback(edits, diagnostic);
     });
 
     return {
diff --git a/src/util/references.ts b/src/util/references.ts
index 425ba08..8e06872 100644
--- a/src/util/references.ts
+++ b/src/util/references.ts
@@ -29,17 +29,14 @@ export class References {
         switch (definitionNode.nodeType) {
           case "Function":
             {
-              if (definitionNode.node.parent) {
-                const annotationNameNode = TreeUtils.getTypeAnnotation(
-                  definitionNode.node.parent,
-                )?.childForFieldName("name");
-
-                if (annotationNameNode && refSourceTree.writeable) {
-                  references.push({
-                    node: annotationNameNode,
-                    uri: definitionNode.uri,
-                  });
-                }
+              const annotationNameNode = this.getFunctionAnnotationNameNodeFromDefinition(
+                definitionNode.node,
+              );
+              if (annotationNameNode && refSourceTree.writeable) {
+                references.push({
+                  node: annotationNameNode,
+                  uri: definitionNode.uri,
+                });
               }
 
               const functionNameNode = TreeUtils.getFunctionNameNodeFromDefinition(
@@ -758,6 +755,21 @@ export class References {
     return TreeUtils.descendantsOfType(node, "record_base_identifier");
   }
 
+  private static getFunctionAnnotationNameNodeFromDefinition(
+    node: SyntaxNode,
+  ): SyntaxNode | undefined {
+    if (
+      node.parent &&
+      node.parent.previousNamedSibling &&
+      node.parent.previousNamedSibling.type === "type_annotation" &&
+      node.parent.previousNamedSibling.firstChild &&
+      node.parent.previousNamedSibling.firstChild.type ===
+        "lower_case_identifier"
+    ) {
+      return node.parent.previousNamedSibling.firstChild;
+    }
+  }
+
   private static findFieldUsages(tree: Tree, fieldName: string): SyntaxNode[] {
     return tree.rootNode
       .descendantsOfType([
diff --git a/src/util/types/typeChecker.ts b/src/util/types/typeChecker.ts
index eac57c2..a4cb9da 100644
--- a/src/util/types/typeChecker.ts
+++ b/src/util/types/typeChecker.ts
@@ -571,7 +571,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       // The function name should resolve to itself
       if (nodeParent.firstNamedChild?.text === nodeText) {
         return {
-          node: nodeParent,
+          node: nodeParent.parent,
           nodeType: "Function",
           uri: treeContainer.uri,
         };
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index 59e32a3..4cf5323 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -1473,7 +1473,7 @@ export class InferenceScope {
       }
     }
 
-    const type = targetTy.fields[fieldIdentifierText] ?? TVar("a");
+    const type = targetTy.fields[fieldIdentifierText] ?? TUnknown;
     this.expressionTypes.set(expr, type);
     return type;
   }
@@ -1592,7 +1592,6 @@ export class InferenceScope {
               record,
               this.calculateRecordDiff(TRecord(mappedFields), baseType),
             );
-            this.expressionTypes.set(field, type);
           }
         } else if (baseType.nodeType === "MutableRecord") {
           baseType.fields[fieldText] = type;
@@ -1922,7 +1921,11 @@ export class InferenceScope {
   ): void {
     const ty = this.bindIfVar(listPattern, type, TList(TVar("a")));
 
-    if (ty.nodeType !== "Union" || !typeIsList(ty)) {
+    if (
+      ty.nodeType === "Unknown" ||
+      ty.nodeType !== "Union" ||
+      !typeIsList(ty)
+    ) {
       if (ty.nodeType !== "Unknown") {
         this.diagnostics.push(
           typeMismatchError(
@@ -1943,8 +1946,7 @@ export class InferenceScope {
     const innerType = ty.params[0];
 
     parts.slice(0, parts.length - 1).forEach((part) => {
-      const t = part.nodeType === "ListPattern" ? ty : innerType;
-      this.bindPattern(part, t, false);
+      this.bindPattern(part, innerType, false);
     });
 
     if (parts.length > 0) {


Test patch:
diff --git a/test/codeActionProvider.test.ts b/test/codeActionProvider.test.ts
new file mode 100644
index 0000000..f9e25bc
--- /dev/null
+++ b/test/codeActionProvider.test.ts
@@ -0,0 +1,190 @@
+import { container } from "tsyringe";
+import { CodeAction } from "vscode-languageserver";
+import { URI } from "vscode-uri";
+import { IElmWorkspace } from "../src/elmWorkspace";
+import {
+  CodeActionProvider,
+  convertFromAnalyzerDiagnostic,
+} from "../src/providers";
+import { ICodeActionParams } from "../src/providers/paramsExtensions";
+import { Utils } from "../src/util/utils";
+import { getTargetPositionFromSource } from "./utils/sourceParser";
+import { baseUri, SourceTreeParser } from "./utils/sourceTreeParser";
+
+function codeActionEquals(a: CodeAction, b: CodeAction): boolean {
+  return a.title === b.title;
+}
+
+const basicsSources = `
+--@ Basics.elm
+module Basics exposing ((+), (|>), (==), Int, Float, Bool(..), Order(..))
+
+infix left  0 (|>) = apR
+infix non   4 (==) = eq
+infix left  6 (+)  = add
+
+type Int = Int
+
+type Float = Float
+
+type Bool = True | False
+
+add : number -> number -> number
+add =
+  Elm.Kernel.Basics.add
+
+apR : a -> (a -> b) -> b
+apR x f =
+  f x
+
+eq : a -> a -> Bool
+eq =
+  Elm.Kernel.Utils.equal
+
+type Order = LT | EQ | GT
+`;
+
+class MockCodeActionsProvider extends CodeActionProvider {
+  public handleCodeAction(params: ICodeActionParams): CodeAction[] | undefined {
+    return this.onCodeAction(params);
+  }
+}
+
+describe("test codeActionProvider", () => {
+  const treeParser = new SourceTreeParser();
+  let codeActionProvider: MockCodeActionsProvider;
+
+  const debug = process.argv.find((arg) => arg === "--debug");
+
+  async function testCodeAction(
+    source: string,
+    expectedCodeActions: CodeAction[],
+  ) {
+    await treeParser.init();
+
+    if (!codeActionProvider) {
+      codeActionProvider = new MockCodeActionsProvider();
+    }
+
+    const result = getTargetPositionFromSource(source);
+
+    if (!result) {
+      throw new Error("Getting sources failed");
+    }
+
+    const testUri = URI.file(baseUri + "Test.elm").toString();
+
+    const program = await treeParser.getProgram(result.sources);
+    const sourceFile = program.getForest().getByUri(testUri);
+
+    if (!sourceFile) throw new Error("Getting tree failed");
+
+    const workspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
+    workspaces.splice(0, workspaces.length);
+    workspaces.push(program);
+
+    const range = { start: result.position, end: result.position };
+    const codeActions =
+      codeActionProvider.handleCodeAction({
+        program,
+        sourceFile,
+        range,
+        textDocument: { uri: testUri },
+        context: {
+          diagnostics: [
+            ...program.getSyntacticDiagnostics(sourceFile),
+            ...program.getSemanticDiagnostics(sourceFile),
+            ...program.getSuggestionDiagnostics(sourceFile),
+          ]
+            .filter((diag) => Utils.rangeOverlaps(diag.range, range))
+            .map(convertFromAnalyzerDiagnostic),
+        },
+      }) ?? [];
+
+    const codeActionsExist = expectedCodeActions.every((codeAction) =>
+      codeActions.find((c) => codeActionEquals(codeAction, c)),
+    );
+
+    if (debug && !codeActionsExist) {
+      console.log(
+        `Expecting ${JSON.stringify(expectedCodeActions)}, got ${JSON.stringify(
+          codeActions,
+        )}`,
+      );
+    }
+
+    expect(codeActionsExist).toBeTruthy();
+  }
+
+  test("add import of value", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func = foo
+      --^
+
+--@ App.elm
+module App exposing (foo)
+
+foo = ""
+`;
+    await testCodeAction(basicsSources + source, [
+      { title: `Import 'foo' from module "App"` },
+    ]);
+  });
+
+  test("add import of qualified value", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func = App.foo
+          --^
+
+--@ App.elm
+module App exposing (foo)
+
+foo = ""
+`;
+    await testCodeAction(basicsSources + source, [
+      { title: `Import module "App"` },
+    ]);
+
+    const source2 = `
+--@ Test.elm
+module Test exposing (..)
+
+func = App.foo
+      --^
+
+--@ App.elm
+module App exposing (foo)
+
+foo = ""
+`;
+    await testCodeAction(basicsSources + source2, [
+      { title: `Import module "App"` },
+    ]);
+  });
+
+  test("add all missing imports", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func = foo + bar
+      --^
+
+--@ App.elm
+module App exposing (foo, bar)
+
+foo = ""
+
+bar = ""
+`;
+    await testCodeAction(basicsSources + source, [
+      { title: `Add all missing imports` },
+    ]);
+  });
+});
diff --git a/test/codeActionTests/addMissingRecordFieldCodeAction.test.ts b/test/codeActionTests/addMissingRecordFieldCodeAction.test.ts
deleted file mode 100644
index 657f641..0000000
--- a/test/codeActionTests/addMissingRecordFieldCodeAction.test.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { testCodeAction } from "./codeActionTestBase";
-
-describe("add missing record field code action", () => {
-  it("should work with type alias on a single line", async () => {
-    const source = `
-		--@ Test.elm
-module Test exposing (..)
-
-type alias Model = { field : Int }
-
-func : Model -> Float
-func model =
-		model.newProp
-			     --^
-		`;
-
-    const expectedSource = `
-		--@ Test.elm
-module Test exposing (..)
-
-type alias Model = { field : Int, newProp : Float }
-
-func : Model -> Float
-func model =
-		model.newProp
-		`;
-
-    await testCodeAction(
-      source,
-      [{ title: "Create missing record field" }],
-      expectedSource,
-    );
-  });
-
-  it("should work with type alias on a multiple lines", async () => {
-    const source = `
-		--@ Test.elm
-module Test exposing (..)
-
-type alias Model = 
-  { field : Int
-	}
-
-func : Model -> { prop : Int }
-func model =
-		model.newProp
-			     --^
-		`;
-
-    const expectedSource = `
-		--@ Test.elm
-module Test exposing (..)
-
-type alias Model = 
-  { field : Int
-  , newProp : { prop : Int }
-	}
-
-func : Model -> { prop : Int }
-func model =
-		model.newProp
-		`;
-
-    await testCodeAction(
-      source,
-      [{ title: "Create missing record field" }],
-      expectedSource,
-    );
-  });
-
-  it("should work with record update expr", async () => {
-    const source = `
-		--@ Test.elm
-module Test exposing (..)
-
-func : { field : Int } -> a
-func model =
-		{ model | newProp = "" } 
-			         --^
-		`;
-
-    const expectedSource = `
-		--@ Test.elm
-module Test exposing (..)
-
-func : { field : Int, newProp : String } -> a
-func model =
-		{ model | newProp = "" } 
-		`;
-
-    await testCodeAction(
-      source,
-      [{ title: "Create missing record field" }],
-      expectedSource,
-    );
-  });
-});
diff --git a/test/codeActionTests/codeActionTestBase.ts b/test/codeActionTests/codeActionTestBase.ts
deleted file mode 100644
index 69569c1..0000000
--- a/test/codeActionTests/codeActionTestBase.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import { container } from "tsyringe";
-import { CodeAction } from "vscode-languageserver";
-import { URI } from "vscode-uri";
-import { IElmWorkspace } from "../../src/elmWorkspace";
-import {
-  CodeActionProvider,
-  convertFromAnalyzerDiagnostic,
-} from "../../src/providers";
-import { ICodeActionParams } from "../../src/providers/paramsExtensions";
-import { Utils } from "../../src/util/utils";
-import {
-  getTargetPositionFromSource,
-  getSourceFiles,
-} from "../utils/sourceParser";
-import {
-  SourceTreeParser,
-  trimTrailingWhitespace,
-  baseUri,
-  applyEditsToSource,
-  stripCommentLines,
-} from "../utils/sourceTreeParser";
-
-function codeActionEquals(a: CodeAction, b: CodeAction): boolean {
-  return a.title === b.title;
-}
-
-const basicsSources = `
---@ Basics.elm
-module Basics exposing ((+), (|>), (==), Int, Float, Bool(..), Order(..))
-
-infix left  0 (|>) = apR
-infix non   4 (==) = eq
-infix left  6 (+)  = add
-
-type Int = Int
-
-type Float = Float
-
-type Bool = True | False
-
-add : number -> number -> number
-add =
-  Elm.Kernel.Basics.add
-
-apR : a -> (a -> b) -> b
-apR x f =
-  f x
-
-eq : a -> a -> Bool
-eq =
-  Elm.Kernel.Utils.equal
-
-type Order = LT | EQ | GT
-`;
-
-class MockCodeActionsProvider extends CodeActionProvider {
-  public handleCodeAction(params: ICodeActionParams): CodeAction[] | undefined {
-    return this.onCodeAction(params);
-  }
-}
-
-export async function testCodeAction(
-  source: string,
-  expectedCodeActions: CodeAction[],
-  expectedResultAfterEdits?: string,
-): Promise<void> {
-  const treeParser = new SourceTreeParser();
-  await treeParser.init();
-  const codeActionProvider = new MockCodeActionsProvider();
-
-  const debug = process.argv.find((arg) => arg === "--debug");
-
-  const result = getTargetPositionFromSource(
-    basicsSources + trimTrailingWhitespace(source),
-  );
-
-  if (!result) {
-    throw new Error("Getting sources failed");
-  }
-
-  const testUri = URI.file(baseUri + "Test.elm").toString();
-
-  const program = await treeParser.getProgram(result.sources);
-  const sourceFile = program.getForest().getByUri(testUri);
-
-  if (!sourceFile) throw new Error("Getting tree failed");
-
-  const workspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
-  workspaces.splice(0, workspaces.length);
-  workspaces.push(program);
-
-  const range = { start: result.position, end: result.position };
-  const codeActions =
-    codeActionProvider.handleCodeAction({
-      program,
-      sourceFile,
-      range,
-      textDocument: { uri: testUri },
-      context: {
-        diagnostics: [
-          ...program.getSyntacticDiagnostics(sourceFile),
-          ...program.getSemanticDiagnostics(sourceFile),
-          ...program.getSuggestionDiagnostics(sourceFile),
-        ]
-          .filter((diag) => Utils.rangeOverlaps(diag.range, range))
-          .map(convertFromAnalyzerDiagnostic),
-      },
-    }) ?? [];
-
-  const codeActionsExist = expectedCodeActions.every((codeAction) =>
-    codeActions.find((c) => codeActionEquals(codeAction, c)),
-  );
-
-  if (debug && !codeActionsExist) {
-    console.log(
-      `Expecting ${JSON.stringify(expectedCodeActions)}, got ${JSON.stringify(
-        codeActions,
-      )}`,
-    );
-  }
-
-  expect(codeActionsExist).toBeTruthy();
-
-  if (expectedResultAfterEdits) {
-    const expectedSources = getSourceFiles(
-      trimTrailingWhitespace(expectedResultAfterEdits),
-    );
-
-    Object.entries(expectedSources).forEach(([uri, source]) => {
-      expect(
-        applyEditsToSource(
-          stripCommentLines(result.sources[uri]),
-          codeActions[0].edit?.changes![URI.file(baseUri + uri).toString()] ??
-            [],
-        ),
-      ).toEqual(source);
-    });
-  }
-}
diff --git a/test/codeActionTests/importCodeAction.test.ts b/test/codeActionTests/importCodeAction.test.ts
deleted file mode 100644
index 0c1edbc..0000000
--- a/test/codeActionTests/importCodeAction.test.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { testCodeAction } from "./codeActionTestBase";
-
-describe("import code actions", () => {
-  test("add import of value", async () => {
-    const source = `
---@ Test.elm
-module Test exposing (..)
-
-func = foo
-      --^
-
---@ App.elm
-module App exposing (foo)
-
-foo = ""
-`;
-    await testCodeAction(source, [{ title: `Import 'foo' from module "App"` }]);
-  });
-
-  test("add import of qualified value", async () => {
-    const source = `
---@ Test.elm
-module Test exposing (..)
-
-func = App.foo
-          --^
-
---@ App.elm
-module App exposing (foo)
-
-foo = ""
-`;
-    await testCodeAction(source, [{ title: `Import module "App"` }]);
-
-    const source2 = `
---@ Test.elm
-module Test exposing (..)
-
-func = App.foo
-      --^
-
---@ App.elm
-module App exposing (foo)
-
-foo = ""
-`;
-    await testCodeAction(source2, [{ title: `Import module "App"` }]);
-  });
-
-  test("add all missing imports", async () => {
-    const source = `
---@ Test.elm
-module Test exposing (..)
-
-func = foo + bar
-      --^
-
---@ App.elm
-module App exposing (foo, bar)
-
-foo = ""
-
-bar = ""
-`;
-    await testCodeAction(source, [{ title: `Add all missing imports` }]);
-  });
-});
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index 8879c6e..a5df029 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -809,4 +809,27 @@ foo input =
   `;
     await testTypeInference(basicsSources + stringSources + source, []);
   });
+
+  test("test cons pattern with list", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+test : List (List Float) -> Float
+test listList =
+    case listList of
+        [ a, b ] :: _ ->
+            let
+                product : Float
+                product =
+                    a + b
+            in
+            product
+
+        _ ->
+            0
+
+  `;
+    await testTypeInference(basicsSources + source, []);
+  });
 });
diff --git a/test/referencesProviderTests/functionReferences.test.ts b/test/referencesProviderTests/functionReferences.test.ts
index 39585ee..c94837b 100644
--- a/test/referencesProviderTests/functionReferences.test.ts
+++ b/test/referencesProviderTests/functionReferences.test.ts
@@ -104,7 +104,8 @@ bar = foo
     await testBase.testReferences(source);
   });
 
-  it(`function annotation gets a reference`, async () => {
+  // getFunctionAnnotationNameNodeFromDefinition doesn't find the annotation due to the comment inbetween
+  xit(`function annotation gets a reference`, async () => {
     const source = `
 
 --@ Module.elm