elm-in-elm / compiler / Elm.Compiler

Functions for working with Elm source code.

The compiler phases in general look like this:

Stages of the compiler

Parsing

Useful eg. for tools like elm-format that convert back to the Elm representation, trying to do as few changes as possible. Ie. it would be bad if elm-format changed

\a b c -> a + b + c

to

\a -> \b -> \c -> a + b + c

That transformation is one of the things the Desugar phase does. So tools like elm-format probably don't want to touch that phase, and will only want to parse!

parseExpr : Elm.Data.FileContents.FileContents -> Result Error Elm.AST.Frontend.LocatedExpr

Parse a single expression like

( 12, "Hello" )

into AST like

Located
    {start = ..., end = ...}
    (Tuple
        (Located ... (Int 12))
        (Located ... (String "Hello"))
    )

If you don't need the location information and want to only keep the expressions, use Elm.AST.Frontend.unwrap to get something like

Tuple
    (Int 12)
    (String "Hello")

parseModule : { filePath : Elm.Data.FilePath.FilePath, sourceCode : Elm.Data.FileContents.FileContents } -> Result Error (Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr)

Parse a module (one *.elm file). Get a Module datastructure back, holding the information about its exposed values, imports, declarations and more.

A file like

module Main exposing (foo)

import Bar as B exposing (bar)

foo =
    123

will get parsed into

{ imports =
    Dict.fromList
        [ ( "Bar"
          , { moduleName = "Bar"
            , as_ = Just "B"
            , exposing_ = Just (ExposingSome [ ExposedValue "bar" ])
            }
          )
        ]
, name = "Foo"
, filePath = "src/Foo.elm" -- what you pass into the function
, declarations =
    Dict.fromList
        [ ( "foo"
          , { module_ = "Foo"
            , name = "foo"
            , body = Value (AST.Frontend.Int 123)
            }
          )
        ]
, type_ = PlainModule
, exposing_ = ExposingSome [ ExposedValue "foo" ]
}

parseModules : List { filePath : Elm.Data.FilePath.FilePath, sourceCode : Elm.Data.FileContents.FileContents } -> Result Error (Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr))

Parse multiple modules (*.elm files) - see parseModule for details.

parseImport : Elm.Data.FileContents.FileContents -> Result Error Elm.Data.Import.Import

Parse a single import statement, like

import Foo as F exposing
    ( foo
    , Bar(..)
    , Baz
    )

into

{ moduleName = "Foo"
, as_ = Just "F"
, exposing_ =
    Just
        (ExposingSome
            [ ExposedValue "foo"
            , ExposedTypeAndAllConstructors "Bar"
            , ExposedType "Baz"
            ]
        )
}

parseDeclaration : { moduleName : Elm.Data.ModuleName.ModuleName, declaration : Elm.Data.FileContents.FileContents } -> Result Error (Elm.Data.Declaration.Declaration Elm.AST.Frontend.LocatedExpr)

Parse a single declaration, like

foo =
    123

into

{ module_ = "Foo" -- what you pass into the function
, name = "foo"
, body = Value (AST.Frontend.Int 123)
}

Desugaring

After we parse the source code from a String to the AST, we desugar it - simplify the AST type as much as possible to make later phases simpler and easier.

The best example to illustrate this (it doesn't actually happen though!) is let vs where. Imagine if Elm allowed for where constructs in its syntax, like Haskell does:

foo = x + y
    where
        x = 123
        y = x + 2

Then we'd like to convert these to let constructs (or the other way round) as soon as possible, so that the other phases don't need to handle two almost identical scenarios all over the place.

Examples of real desugarings include:

desugarExpr : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr) -> Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr -> Elm.AST.Frontend.LocatedExpr -> Result Error Elm.AST.Canonical.LocatedExpr

Desugar a single expression like

Lambda
    { arguments = [ "x", "y" ]
    , body = AST.Frontend.Int 42
    }

into AST like

Lambda
    { argument = "x"
    , body =
        Lambda
            { argument = "y"
            , body = AST.Frontend.Int 42
            }
    }

desugarModule : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr) -> Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr -> Result Error (Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr)

Desugar a module (one *.elm file).

desugarModules : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr) -> Result Error (Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr))

Desugar multiple modules (*.elm files) - see desugarModule for details.

desugarOnlyModule : Elm.Data.Module.Module Elm.AST.Frontend.LocatedExpr -> Result Error (Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr)

Desugar a module (one *.elm file), without the intention of desugaring another one.

Inferring types

These functions compute the types of the given expressions, as well as check them against the user-defined type annotations.

Note that the more of your code you'll give these functions at once, the better the type inference will be. So it's advisable to eg. run inferModules once instead of running inferModule on each of your modules.

inferExpr : Elm.AST.Canonical.LocatedExpr -> Result Error Elm.AST.Typed.LocatedExpr

Infer the types of a single expression.

inferModule : Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr -> Result Error (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr)

Infer the types of expressions in a module (a single *.elm file).

inferModules : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr) -> Result Error (Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr))

Infer the types of expressions in multiple modules (*.elm files).

Optimizing

After typechecking the expressions are ready to be optimized. (The inferred types are available to you inside the optimizations! (Unfortunately, the location information is also available to you inside the optimization. Sorry.))

defaultOptimizations : List ( String, Elm.AST.Typed.LocatedExpr -> Maybe Elm.AST.Typed.LocatedExpr )

The default optimizations the elm-in-elm compiler uses.

Try evaluating it in the REPL: you should see that each optimization function
has a name String next to it.

    > import Elm.Compiler
    > Elm.Compiler.optimizations
    [ ("plus", ...)
    , ("cons", ...)
    , ("if-literal-bool", ...)
    ]

    >

You can use this to filter optimizations you don't want!

   wantedOptimizations = Set.fromList [ "plus", "if-literal-bool ]

   optimizations
       |> List.filter (\(name, _) -> Set.member name wantedOptimizations)

optimizeExpr : Elm.AST.Typed.LocatedExpr -> Elm.AST.Typed.LocatedExpr

Optimize a given (typed) expression using the default set of optimizations.

For using your own optimizations instead of or in addition to the default ones, look at the optimizeExprWith function.

optimizeExprWith : List ( String, Elm.AST.Typed.LocatedExpr -> Maybe Elm.AST.Typed.LocatedExpr ) -> Elm.AST.Typed.LocatedExpr -> Elm.AST.Typed.LocatedExpr

Optimize a given (typed) expression using a custom set of optimizations.

optimizeModule : Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr -> Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr

Optimize all expressions in a given module using the default set of optimizations.

Note there is currently no inter-definition optimizations (inlining etc.) - only the optimizations on each separate expression.

For using your own optimizations instead of or in addition to the default ones, look at the optimizeModuleWith function.

optimizeModuleWith : List ( String, Elm.AST.Typed.LocatedExpr -> Maybe Elm.AST.Typed.LocatedExpr ) -> Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr -> Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr

Optimize all expressions in a given module using a custom set of optimizations.

Note there is currently no inter-definition optimizations (inlining etc.) - only the optimizations on each separate expression.

optimizeModules : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr) -> Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr)

Optimize all expressions in multiple modules using the default set of optimizations.

For using your own optimizations instead of or in addition to the default ones, look at the optimizeModulesWith function.

optimizeModulesWith : List ( String, Elm.AST.Typed.LocatedExpr -> Maybe Elm.AST.Typed.LocatedExpr ) -> Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr) -> Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr)

Optimize all expressions in multiple modules using a custom set of optimizations.

Dropping types

If you want to typecheck the code but then don't do anything with the types afterwards, you can drop them from the expressions you have. This is essentially a move backwards in the compiler phases:

Stages of the compiler

dropTypesExpr : Elm.AST.Typed.LocatedExpr -> Elm.AST.Canonical.LocatedExpr

Drop types from a single expression.

We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:

Typed.LocatedExpr -> Canonical.LocatedExpr

Example usage:

Located
    { start = ..., end = ... }
    ( Tuple
        (Located ... ( Int 12, Type.Int ))
        (Located ... ( String "Hello", Type.String ))
    , Type.Tuple Type.Int Type.String
    )

becomes

Located
    { start = ..., end = ... }
    (Tuple
        (Located ... (Int 12)
        (Located ... (String "Hello")
    )

If location info is not useful to you either, look for the unwrap functions in the various Elm.AST.* modules.

dropTypesModule : Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr -> Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr

Drop types from all expressions in the module.

We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:

Module Typed.LocatedExpr
-> Module Canonical.LocatedExpr

dropTypesModules : Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Typed.LocatedExpr) -> Dict Elm.Data.ModuleName.ModuleName (Elm.Data.Module.Module Elm.AST.Canonical.LocatedExpr)

Drop types from all expressions in all the modules.

We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:

Dict ModuleName (Module Typed.LocatedExpr)
-> Dict ModuleName (Module Canonical.LocatedExpr)