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

Issue:
Type inference error with Tuples and float/ints
If you destructure a number from a tuple, the type-inference seems to not be able to treat it as both a float and an int.

```elm
module Main exposing (foo)

foo asInt =
    let
        myGoodNumber =
            216

        ( myBadNumber, _ ) =
            ( 216, 0 )
    in

    if asInt then
        String.fromInt myGoodNumber
            ++ String.fromInt myBadNumber

    else
        String.fromFloat myGoodNumber
            ++ String.fromFloat myBadNumber
```

## Expected Behavior
`elm make` doesn't compile with any errors here.

## Current Behavior
`elm-language-server` throws an error:
<img width="1075" alt="CleanShot 2022-05-23 at 13 28 32@2x" src="https://user-images.githubusercontent.com/446007/169875136-19da07aa-8d5f-43b0-8eec-780068b4fc39.png">

## Context
It seems that Elm does some type-punning for constant numbers, and lets them be both Floats and Ints. The type inference code here seems to mostly do the same thing, except when destructuring those numbers. Perhaps this breaks for record and opaque type destructuring as well?


Pull request:
Fix type inference of non-rigid number vars
Fixes #697. This is also a bug in intelij-elm. I am not fully confident with this fix, mostly because the compiler behavior here is a little confusing, but it passes all tests so I think it should be fine.

Patch:
diff --git a/src/compiler/typeInference.ts b/src/compiler/typeInference.ts
index cb6fe31..06aec61 100644
--- a/src/compiler/typeInference.ts
+++ b/src/compiler/typeInference.ts
@@ -2475,7 +2475,10 @@ export class InferenceScope {
           TMutableRecord(Object.assign({}, type2.fields), type2.baseType),
         );
       } else {
-        assign(type1, type2);
+        const typeClass1 = getTypeclassName(type1);
+        if (type1.rigid || !typeClass1) {
+          assign(type1, type2);
+        }
       }
     }
   }


Test patch:
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index ea1c7db..7f1b2e7 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -21,10 +21,11 @@ import { describe, expect } from "@jest/globals";
 
 const basicsSources = `
 --@ Basics.elm
-module Basics exposing ((+), (|>), (>>), (==), (>), Int, Float, Bool(..), Order(..), negate)
+module Basics exposing ((+), (|>), (>>), (==), (>), (++), Int, Float, Bool(..), Order(..), negate)
 
 infix left  0 (|>) = apR
 infix non   4 (==) = eq
+infix right 5 (++) = append
 infix left  6 (+)  = add
 infix right 9 (>>) = composeR
 infix non   4 (>)  = gt
@@ -60,13 +61,25 @@ type Order = LT | EQ | GT
 negate : number -> number
 negate n =
   -n
+
+append : appendable -> appendable -> appendable
+append =
+  Elm.Kernel.Utils.append
 `;
 
 const stringSources = `
 --@ String.elm
-module String exposing (String)
+module String exposing (String, fromInt, fromFloat)
 
 type String = String
+
+fromInt : Int -> String
+fromInt =
+  \\_ -> ""
+
+fromFloat : Float -> String
+fromFloat =
+  \\_ -> ""
 `;
 
 describe("test elm diagnostics", () => {
@@ -1520,4 +1533,58 @@ type DataType
 `;
     await testTypeInference(basicsSources + source, []);
   });
+
+  it("should not have an error when converting a number to Float or Int", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+import String
+
+foo : Bool -> String
+foo asInt =
+    let
+        myGoodNumber =
+            216
+
+        ( myBadNumber, _ ) =
+            ( 216, 0 )
+    in
+    if asInt then
+        String.fromInt myGoodNumber
+            ++ String.fromInt myBadNumber
+
+    else
+        String.fromFloat myGoodNumber
+            ++ String.fromFloat myBadNumber
+`;
+    await testTypeInference(basicsSources + stringSources + source, []);
+  });
+
+  it("should have an error when converting a type annotation number to Float or Int", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+import String
+
+foo : Bool -> number -> String
+foo asInt myNumber =
+    let
+      myGoodNumber =
+        216
+    in
+    if asInt then
+        String.fromInt myNumber
+            ++ String.fromInt myGoodNumber
+
+    else
+        String.fromFloat myNumber
+                        --^
+            ++ String.fromFloat myGoodNumber
+`;
+    await testTypeInference(basicsSources + stringSources + source, [
+      { message: Diagnostics.TypeMismatch, args: ["Float", "Int"] },
+    ]);
+  });
 });