lue-bird/elm-syntax-type-infer/2.0.7

NOTE: the latest version is 2.0.8

ElmSyntaxTypeInfer

Add type information to the nodes of an elm-syntax tree.

{ moduleName : String
, importedTypes : Dict String ModuleTypes
, moduleOriginLookup : ModuleOriginLookup
, otherModuleDeclaredTypes : ModuleTypes
}
-> List Function
-> Result
String
(List
{ parameters : List (TypedNode Pattern)
, result : TypedNode Expression
, type_ : Type
, name : String
, nameRange : Range
, documentation :
Maybe
{ content : String
, range : Range
}
, signature :
Maybe
{ range : Range
, nameRange : Range
, annotationTypeRange : Range
, annotationType : TypeAnnotation
}
}
)

Infer types of value/Elm.Syntax.Expression.Function declarations in a module.

import Elm.Syntax.Node
import Elm.Syntax.Expression
import ElmSyntaxTypeInfer




[ { declaration =
        Elm.Syntax.Node.empty
            { name = Elm.Syntax.Node.empty "hello"
            , expression =
                Elm.Syntax.Node.empty
                    (Elm.Syntax.Expression.Literal "world")
            , arguments = []
            }
  , signature = Nothing
  , documentation = Nothing
  }
]
    |> ElmSyntaxTypeInfer.valueAndFunctionDeclarations
        { importedTypes = ElmSyntaxTypeInfer.elmCoreTypes
        , moduleOriginLookup = exampleModuleOriginLookup
        , otherModuleDeclaredTypes =
            []
                |> ElmSyntaxTypeInfer.moduleDeclarationsToTypes
                    exampleModuleOriginLookup
                |> .types
        }
-->
Ok
    [ { name = "hello"
      , type_ =
            ElmSyntaxTypeInfer.TypeNotVariable
                (ElmSyntaxTypeInfer.TypeConstruct
                    { moduleOrigin = [ "String" ], name = "String", arguments = [] }
                )
      ...
      }
    ]




exampleModuleOriginLookup : ElmSyntaxTypeInfer.ModuleOriginLookup
exampleModuleOriginLookup =
    []
        |> ElmSyntaxTypeInfer.importsToModuleOriginLookup
            ElmSyntaxTypeInfer.elmCoreTypes

See ModuleTypes and ModuleOriginLookup

Warning! Always first verify that provided type alias declarations aren't (mutually) recursive. Otherwise this function might never terminate.

context

type alias ModuleTypes =
{ signatures : Dict String Type
, typeAliases :
Dict
String
{ parameters : List String
, type_ : Type
, recordFieldOrder : Maybe (List String)
}
, choiceTypes :
Dict
String
{ parameters : List String
, variants : Dict String (List Type)
}
}

Known types of members in a module. Create with moduleDeclarationsToTypes and moduleInterfaceToTypes

elmCoreTypes : Dict String ModuleTypes

ModuleTypes exposed in elm/core.

Please always start with elmCoreTypes and add further module info with FastDict.union using moduleDeclarationsToTypes and moduleInterfaceToTypes

And if for some reason you already know used dependencies at compile time, you can re-use the code generator used for these elm/core types

{ moduleOriginLookup : ModuleOriginLookup
, moduleName : String
}
-> List (Node Declaration)
-> { types : ModuleTypes
, errors : List String
}

Extract all known types from declarations within a module. Un-annotated value/function declarations are ignored.

For dependency modules, use moduleInterfaceToTypes

Module
-> { types : ModuleTypes
, errors : List String
}

Extract all known types from module metadata, usually from parsing the docs.json of a dependency.

For elm-syntax modules, use moduleDeclarationsToTypes

type alias ModuleOriginLookup =
{ references : Dict String (Dict String String)
, typeConstructs : Dict String (Dict String String)
, keepOperatorIsExposedFromParserAdvanced : Bool
, ignoreOperatorIsExposedFromParserAdvanced : Bool
}

How to map names and symbols used in a module to their origin module. Create with importsToModuleOriginLookup.

Contains variants, type alias names, choice type names, port names, value/function declaration names and whether (|.) and or (|=) are imported from Parser.Advanced.

Also contains locally declared names when available.

Type- and expression/pattern-level members are separated since you can for example have a local List variant while also using an (implicitly) imported List type.

importsToModuleOriginLookup : Dict String ModuleTypes -> List (Node Import) -> ModuleOriginLookup

Calculate valid mappings of qualifications + name to origin module based on a module's imports, see ModuleOriginLookup.

Requires all exposed ModuleTypes so we can resolve exposing (..) and ChoiceType(..).

syntax

type alias TypedNode value =
{ range : Range
, type_ : Type
, value : value
}

A part in the syntax tree with an attached

  • range in the source
  • global Type: concretely inferred, influenced by other branches and value/function (let) declaration type annotations
= ExpressionUnit
| ExpressionInteger
{ base : Base10Or16
, value : Int
}
| ExpressionFloat Float
| ExpressionString String
| ExpressionChar Char
| ExpressionReference
{ moduleOrigin : String
, qualification : String
, name : String
}
| ExpressionReferenceVariant
{ moduleOrigin : String
, choiceTypeName : String
, qualification : String
, name : String
}
| ExpressionReferenceRecordTypeAliasConstructorFunction
{ moduleOrigin : String
, qualification : String
, name : String
}
| ExpressionOperatorFunction
{ moduleOrigin : String
, symbol : String
}
| ExpressionRecordAccessFunction String
| ExpressionNegation (TypedNode Expression)
| ExpressionParenthesized (TypedNode Expression)
| ExpressionTuple
{ part0 : TypedNode Expression
, part1 : TypedNode Expression
}
| ExpressionTriple
{ part0 : TypedNode Expression
, part1 : TypedNode Expression
, part2 : TypedNode Expression
}
| ExpressionRecordAccess
{ record : TypedNode Expression
, fieldNameRange : Range
, fieldName : String
}
| ExpressionInfixOperation
{ operator :
{ moduleOrigin : String
, symbol : String
, type_ : Type
}
, left : TypedNode Expression
, right : TypedNode Expression
}
| ExpressionIfThenElse
{ condition : TypedNode Expression
, onTrue : TypedNode Expression
, onFalse : TypedNode Expression
}
| ExpressionList (List (TypedNode Expression))
| ExpressionCall
{ called : TypedNode Expression
, argument0 : TypedNode Expression
, argument1Up : List (TypedNode Expression)
}
| ExpressionRecord
(List
{ range : Range
, name : String
, nameRange : Range
, value : TypedNode Expression
}
)
| ExpressionRecordUpdate
{ recordVariable :
TypedNode
{ moduleOrigin : String
, name : String
}
, field0 :
{ range : Range
, name : String
, nameRange : Range
, value : TypedNode Expression
}
, field1Up :
List
{ range : Range
, name : String
, nameRange : Range
, value : TypedNode Expression
}
}
| ExpressionLambda
{ parameter0 : TypedNode Pattern
, parameter1Up : List (TypedNode Pattern)
, result : TypedNode Expression
}
| ExpressionCaseOf
{ matched : TypedNode Expression
, case0 :
{ pattern : TypedNode Pattern
, result : TypedNode Expression
}
, case1Up :
List
{ pattern : TypedNode Pattern
, result : TypedNode Expression
}
}
| ExpressionLetIn
{ declaration0 :
{ range : Range
, declaration : LetDeclaration
}
, declaration1Up :
List
{ range : Range
, declaration : LetDeclaration
}
, result : TypedNode Expression
}

Like Elm.Syntax.Expression.Expression but its sub-nodes are TypedNodes.

Be aware when trying to match references that they are split into multiple variants:

  • ExpressionReference (for let/module declared value/function or port or pattern variable use)
  • ExpressionReferenceVariant
  • ExpressionReferenceRecordTypeAliasConstructorFunction
= LetDestructuring
{ pattern : TypedNode Pattern
, expression : TypedNode Expression
}
| LetValueOrFunctionDeclaration
{ name : String
, nameRange : Range
, signature :
Maybe
{ range : Range
, nameRange : Range
, annotationType : TypeAnnotation
, annotationTypeRange : Range
}
, parameters : List (TypedNode Pattern)
, result : TypedNode Expression
, type_ : Type
}
type Pattern
= PatternIgnored
| PatternVariable String
| PatternParenthesized (TypedNode Pattern)
| PatternAs
{ pattern : TypedNode Pattern
, variable : TypedNode String
}
| PatternUnit
| PatternChar Char
| PatternString String
| PatternInt
{ base : Base10Or16
, value : Int
}
| PatternTuple
{ part0 : TypedNode Pattern
, part1 : TypedNode Pattern
}
| PatternTriple
{ part0 : TypedNode Pattern
, part1 : TypedNode Pattern
, part2 : TypedNode Pattern
}
| PatternRecord (List (TypedNode String))
| PatternListCons
{ head : TypedNode Pattern
, tail : TypedNode Pattern
}
| PatternListExact (List (TypedNode Pattern))
| PatternVariant
{ moduleOrigin : String
, choiceTypeName : String
, qualification : String
, name : String
, values : List (TypedNode Pattern)
}

Like Elm.Syntax.Pattern.Pattern but all its sub-nodes are TypedNodes

= Base10
| Base16

Either decimal or hexadecimal. Used by PatternInt and ExpressionInteger

type Type
= TypeVariable
{ useRange : Range
, name : String
}
| TypeNotVariable TypeNotVariable

Type information attached to expressions and patterns, see TypedNode.

This is different from Elm.Syntax.TypeAnnotation.TypeAnnotation in that it doesn't contain information unrelated to type inference like ranges, qualification levels or parens.

Importantly, it also contains the overarching range of all places where each specific type variable is used. This is useful to for example find out whether a variable in a let declaration type is "forall" or inherited from the surrounding context (if the use range is fully included in the let declaration range it's forall, otherwise inherited).

= TypeUnit
| TypeConstruct
{ moduleOrigin : String
, name : String
, arguments : List Type
}
| TypeTuple
{ part0 : Type
, part1 : Type
}
| TypeTriple
{ part0 : Type
, part1 : Type
, part2 : Type
}
| TypeRecord (Dict String Type)
| TypeRecordExtension
{ recordVariable :
{ useRange : Range
, name : String
}
, fields : Dict String Type
}
| TypeFunction
{ input : Type
, output : Type
}

Type except the variable case

If you are interested in exposing helpers like expressionMapType, open an issue