mdgriffith / elm-codegen

https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/
BSD 3-Clause "New" or "Revised" License
137 stars 16 forks source link

Infinite type inference loop - no manual fix possible #90

Open anmolitor opened 3 months ago

anmolitor commented 3 months ago

Hi, first off: thanks for the incredible work on this tool. I wanted to use it to generate some boilerplate maps for non-comparable types and ran into problems:

--ELM CODEGEN WARNING-----------------------------------------------------------
In the generated file: MapIdDict.elm

    When trying to figure out the type for insert, I ran into an issue

    Infinite type inference loop!  Whoops.  This is an issue with elm-codegen.  If you can report this to the elm-codegen repo, that would be appreciated!

    I'm not as smart as the Elm compiler :/, but we're good friends.  I especially get confused when there are a lot of type aliases.
    If you need to, try using Elm.withType to tell me what the type should be!

    generated/MapIdDict.elm was generated!
    However, there was a warning.

For simplicity, I simplified my code to not use any of my custom types but Int instead:

module Generate exposing (main)

{-| -}

import Elm
import Elm.Annotation as Type
import Elm.Case
import Gen.CodeGen.Generate as Generate
import Gen.ManualDict

main : Program {} () ()
main =
    Generate.run
        [ genDict
            { dictTypeName = "MapIdDict"
            , keyType = Type.int
            , comparableType = Type.int
            , toComparable = Basics.identity
            }
        ]

type alias DictConfig =
    { dictTypeName : String
    , keyType : Type.Annotation
    , comparableType : Type.Annotation
    , toComparable : Elm.Expression -> Elm.Expression
    }

genDict : DictConfig -> Elm.File
genDict config =
    let
        v =
            Type.var "v"

        dictAnn =
            Gen.ManualDict.annotation_.dict config.comparableType
                config.keyType
                v

        customAnn =
            Type.namedWith [] config.dictTypeName [ v ]

        wrap dict =
            Elm.apply (Elm.val config.dictTypeName) [ dict ]

        unwrap f dict =
            Elm.Case.custom dict
                (Type.named [] config.dictTypeName)
                [ Elm.Case.branch1 config.dictTypeName
                    ( "dict"
                    , Gen.ManualDict.annotation_.dict
                        config.comparableType
                        config.keyType
                        (Type.var "v")
                    )
                    f
                ]

        withWrap f = unwrap f >> wrap         
    in
    Elm.file [ config.dictTypeName ]
        [ Elm.customTypeWith config.dictTypeName
            [ "v" ]
            [ Elm.variantWith config.dictTypeName [ dictAnn ] ]
        , wrap Gen.ManualDict.empty |> Elm.withType customAnn |> Elm.declaration "empty"
        , Elm.fn3 ( "k", Just config.keyType )
            ( "v", Just v )
            ( "dict"
            , Just customAnn
            )
            (\key value -> withWrap (Gen.ManualDict.insert config.toComparable key value))
            |> Elm.withType (Type.function [ config.keyType, v, customAnn ] customAnn)
            |> Elm.declaration "insert"
        ]

The generated file looks like this:

module MapIdDict exposing (..)

import ManualDict

type MapIdDict v
    = MapIdDict (ManualDict.Dict Int Int v)

empty : MapIdDict v
empty =
    MapIdDict ManualDict.empty

insert k v dict =
    MapIdDict
        (case dict of
             MapIdDict dict0 ->
                 ManualDict.insert (\insertUnpack -> insertUnpack) k v dict0
        )

Note the missing annotation on insert. Even though I specified types on all parameters and the function itself, the issue persists. The code itself is fine, the Elm compiler correctly infers the type.

anmolitor commented 3 months ago

For reproduction, I am using this package: https://package.elm-lang.org/packages/timo-weike/generic-collections/latest/ManualDict

Further investigation showed that the issue is somewhere in "unwrap", since just wrapping compiles just fine.

anmolitor commented 3 months ago

Alright, I've found the issue: In unwrap I have used Type.named [] config.dictTypeName, but I should have used customAnn instead. It would be great if the error messages regarding type inference were a bit more helpful, but it works.

Feel free to close this issue.