Nonsensical error in elm-language-server

I’ve run into something I hadn’t experienced at all while using elm so far - a completely wrong error report.
The code in question is this:

-- ...

import Json.Decode as JD
import Json.Decode.Extra as JD
import Json.Decode.Pipeline as JD

type alias ProductId =
    Int


type alias LabelNumber =
    String


type ProductType
    = MiG
    | Std100
    | Std100PPE
    | Std100SA
    | Std100Recycling
    | Ecopass
    | Step
    | Leather
    | OrganicCotton
    | OrganicCottonBlended


type alias Product =
    { productId : ProductId
    , labelNumber : LabelNumber
    , description : String
    , state : ProductState
    , productType : ProductType
    }


type ProductState
    = Fixed { expiresOn : Time.Posix }
    | Valid { expiresOn : Time.Posix }
    | Expired { expiredOn : Time.Posix }

productDecoder : JD.Decoder Product
productDecoder =
    JD.succeed Product
        |> JD.required "pk" JD.int
        |> JD.required "label_number" JD.string
        |> JD.required "description" JD.string
        |> JD.custom productStateDecoder
        |> JD.required "product" productTypeDecoder


printableProductDecoder : JD.Decoder Product
printableProductDecoder =
    productDecoder
        |> JD.andThen
            (\product ->
                case product.state of
                    Valid _ ->
                        JD.succeed product

                    Fixed _ ->
                        JD.succeed product

                    Expired _ ->
                        JD.fail ("Expired product " ++ product.labelNumber ++ " cannot be printed.")
            )

productStateDecoder : JD.Decoder ProductState
productStateDecoder =
    JD.field "product_state" JD.string
        |> JD.andThen
            (\productState ->
                case productState of
                    "FIXED" ->
                        JD.succeed (\expiresOn -> { expiresOn = expiresOn })
                            |> JD.required "expires_on" JD.datetime
                            |> JD.map Fixed

                    "VALID" ->
                        JD.succeed (\expiresOn -> { expiresOn = expiresOn })
                            |> JD.required "expires_on" JD.datetime
                            |> JD.map Valid

                    "EXPIRED" ->
                        JD.succeed (\expiredOn -> { expiredOn = expiredOn })
                            |> JD.required "expires_on" JD.datetime
                            |> JD.map Expired

                    _ ->
                        JD.fail ("Unrecognized product state: " ++ productState)
            )


productTypeDecoder : JD.Decoder ProductType
productTypeDecoder =
    JD.string
        |> JD.andThen
            (\productType ->
                case productType of
                    "MIG" ->
                        JD.succeed MiG

                    "OTS100" ->
                        JD.succeed Std100

                    "OTS100_PPE" ->
                        JD.succeed Std100PPE

                    "OTS100_SA" ->
                        JD.succeed Std100SA

                    "OTS100_RECYCLING" ->
                        JD.succeed Std100Recycling

                    "ECOPASSPORT" ->
                        JD.succeed Ecopass

                    "STEP" ->
                        JD.succeed Step

                    "LEATHER" ->
                        JD.succeed Leather

                    "ORGANIC_COTTON_BLENDED" ->
                        JD.succeed OrganicCottonBlended

                    "ORGANIC_COTTON" ->
                        JD.succeed OrganicCotton

                    _ ->
                        JD.fail (productType ++ " is not a valid product type")
            )

-- ...

and the language server (tested in VSCode and Helix) complains that the printableProductDecoder functions is wrong:

Type mismatch error.
Expected: Decoder Product
Found: Decoder { productId : ProductId, description : String, productType : ProductType, state : ProductState }

But as can be easily seen, the function just returns the result of the productDecoder function, which is not highlighted.

Also, the elm compiler itself has no problems with this code, and it’s been running in production for a year without problems.

I am running this on NixOS, without globally installed Elm and elm-language-server, in case that makes any difference, but I would not know why.

Any help is greatly appreciated :sweat_smile:

Apparently the error is only displayed when returning an error containing product.labelNumber. If I remove this and just write JD.fail ("Expired product cannot be printed."), no error is reported.

Also, if I instead insert a different field into the string, that field is missing from the would-be return type in the error message, instead of labelNumber. So it seems that some part of the language-server thinks that using the field there consumes it???

Extracting the error branch into its own function also makes the error go away… :thinking:

This gets even weirder. If I edit the file, the error appears, if I save the file without changing anything, it disappears again. So I can do the following:

  1. Type a space; the error appears
  2. Save; elm-format removes the space, the error is still there
  3. Save again; nothing changes, the error disappears

Or

  1. Type a space; the error appears
  2. Remove the space; the error is still there
  3. Save; nothing changes, the error disappears

Could you have unknowingly introduced an unprintable character in your source code?

I would diff the actual bytes before/after the bug manifests.

I noticed that elm-format replaces the non-breaking space character with a verbose and explicit equivalent so that could explain why things work after save.

I don’t think that’s likely here. For one, the saved code passes the elm compiler. And the error reappears when I just type a space. I doubt VSCode would insert a nonbreaking space automatically into a source file. I also can’t diff the versions. If I save without formatting, the error disappears, no changes needed.

I’m not sure how often the Elm Language Server maintainers are active on this forum. Maybe it would be a good idea to open an issue on Issues · elm-tooling/elm-language-server · GitHub (if you haven’t done so already)?

I implied the inverse. You could have some unknown unprintable character in your source, you save and it gets removed.

It’s just speculation and an idea of course, you just need to inspect the bytes to see it it holds

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.