This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Frontends

Frontends compile source code in a programming language into a Morphir IR model.

A frontend reads source documents in one programming language and writes a Morphir IR model. A backend does the opposite: it reads a model and generates code.

LanguageFrontendStatus
F#F# frontendIn process in dotnet-morphir compile, and as the MEP guest morphir-fsharp-frontend for morphir compile --extension. See compile.

1 - F# frontend

Compile a subset of F# into a Morphir IR model.

The F# frontend compiles F# source documents into a Morphir IR model. It writes IR versions 1, 2 and 3 (the classic format) and version 4. It uses the F# compiler service to parse and type-check the source, so a model has the types that F# infers.

The frontend accepts a subset of F#: immutable data and pure functions. It refuses every other construct with an error diagnostic. The coverage reference lists each refused construct, with its code and an example.

Status

The frontend has two forms:

  • In process. Morphir.Frontend.FSharp.FSharpFrontend.registration is an IFrontend from Morphir.Compilation. The dotnet-morphir tool uses it.
  • MEP extension. morphir-fsharp-frontend is a Morphir Extension Protocol (MEP) 0.2.0-draft.1 guest. Hosts that cannot load the F# compiler service, such as the AOT morphir executable, start it as a process. See Run the frontend as a MEP extension.

To compile F# from the command line, see compile.

Input

A compilation gets:

  • a package name, such as acme/car-rental;
  • the modules that the package exposes;
  • the source documents, in compilation order, as F# requires.

Each F# module, nested modules included, becomes one Morphir module. The module path is the namespace and module names without the package path.

namespace Acme.CarRental

module Pricing =
    type Car = { Plate: string; Seats: int }

    type Status =
        | Available
        | Rented of days: int

    let dailyRate (car: Car) : int = if car.Seats > 4 then 60 else 40

    let charge (car: Car) (status: Status) : int =
        match status with
        | Available -> 0
        | Rented days -> dailyRate car * days

Supported subset

AreaSupported
TypesType abbreviations, records, unions with named or positional fields, generic parameters, tuples, functions, unit, bool, int, int64, float, decimal, char, string, option, Result and list
ValuesLiterals, variables, references, constructor application, tuples, lists, record construction, field access, record update, application, lambdas, let, let rec and let rec ... and, if/then/else, and match
PatternsWildcard, variable, literal, tuple, constructor, empty list, cons, as and unit, in match and in function and let parameters
Operators and library functionsArithmetic, comparison, equality, Boolean operators, |>, <|, >> and <<, and mapped functions of List, Option, Result and String

The frontend keeps the order of fields, constructor arguments and match cases, and the currying and evaluation structure of the source. A pipe or a composition becomes a direct application, not a function in the model.

Operators and library functions map to Morphir SDK operations through an explicit table. The type of an operand selects the operation: a + b on decimal values is SDK Decimal.add. A reference with no entry in the table gives fsharp.unsupported_reference and names the F# symbol.

Names and access

F# names become Morphir names: TaxRate and taxRate are both tax rate. Two declarations in one scope with the same Morphir name give fsharp.name_collision. To give a declaration a different Morphir name, use the [<MorphirName("...")>] attribute from the Morphir.FSharp.Annotations package (namespace Morphir). The name is in canonical Morphir syntax.

A module is public when the list of exposed modules names it, or when the compilation has no such list. An empty list exposes no module. An exposed module that matches no compiled module gives compile.unknown_exposed_module. A private or internal declaration is private.

Diagnostics

Diagnostics use the MEP diagnostic shape: a severity, a stable code, a message and a location. Positions are zero-based, and characters count UTF-16 code units. An error stops the compilation, and the frontend writes no model. Warnings and information diagnostics come with a successful model.

F# compiler errors have the code fsharp.compiler. Refused constructs have the codes in the coverage reference. When the frontend cannot give the compiler its references, for example in a self-contained folder whose deps file is missing or unreadable, compilation fails with one fsharp.references_unavailable error that names the cause, and the compiler does not run.

Run the frontend as a MEP extension

Install the guest as a dotnet tool. It needs the .NET runtime.

dotnet tool install --global Morphir.Frontend.FSharp.Extension

The same guest also has a self-contained folder delivery that carries its own .NET runtime, for machines without .NET. See Compile without .NET installed. Both deliveries compile with the same references (the .NET framework, FSharp.Core and Morphir.FSharp.Annotations), so at the same .NET runtime version they give the same IR and diagnostics. The model revision names the frontend identity, which includes the runtime version, so it is equal only on the same runtime patch.

The guest speaks only MEP 0.2.0-draft.1, over stdio with Content-Length framing. It serves morphir.frontend.compileModel and claims:

ClaimValue
Claims version0.1.0-draft.2
Languagefsharp, with the file extension .fs
Several documents in one requestyes (multiDocument: true)
Legacy morphir.frontend.compileno
Incremental compilationno
Model tuplesmorphir.frontend.compileModel, profile morphir-ir, codec morphir-json, envelope 0.1.0-draft.1, IR 4.0.0 and 3.0.0

Classic IR versions 1 and 2 have no tuple. For them, the host compiles at 3.0.0, reads the classic model, and writes it at the requested version. The model does not change family. A host that agrees only 4.0.0 with a guest refuses a classic request before it sends one.

To use the guest from the morphir executable, give its path with --extension, or set it in the project configuration:

morphir compile --extension "$(which morphir-fsharp-frontend)"
[frontend.fsharp]
extension = "morphir-fsharp-frontend"

The host starts the guest with explicit arguments and no shell, and passes the guest’s stderr to its own stderr. Before it sends a request, it checks that the session admits the language, the number of documents and the IR version. It always ends the session with shutdown and exit, or stops the process after a deadline. Guest output on stdout that is not a frame fails the compile with extension.protocol_error.

The model in a successful result is a verified envelope: canonical base64 of the IR JSON, its exact length and its SHA-256. The host checks them before it reads the IR. For the same documents, the IR and the diagnostics from the guest equal the in-process results; a test compares them for every case of the conformance corpus.

Upstream qualification: the MEP guest kit of finos/morphir (generic-json-library-v1) still needs a legacy 0.1 stage and a backend, so the guest is not qualified with it yet. Its conformance comes from the pinned 0.2.0-draft.1 contract, the pinned session transcripts and the model-transport cases. See deviations.

How the frontend is checked

  • The conformance corpus has cases for IR versions 1 to 4 and three input kinds. Each refused construct has its own case. The cases do not depend on the implementation.
  • Behavior cases call the compiled values with JSON arguments and compare the results.
  • A round trip generates F# from each compiled model with the F# backend, compiles that F# again, and requires the same model.

Prior art

The frontend uses the design of two F# projects, but it does not depend on them.

ProjectWhat the frontend usesWhat it does not use, and why
Fable (MIT)Design patterns: how decision trees appear in the typed tree, a replacement table for core-library calls, how inline values and witnesses appear in the typed tree, and a resolver interface for project cracking.Fable.Transforms as a library. It carries Fable’s own syntax tree and replacements for JavaScript, Python and Rust targets.
Fantomas (Apache-2.0)The F# backend formats generated code with Fantomas.Core. The round-trip check uses that backend.Fantomas.FCS in the frontend. Its syntax tree is not binary-compatible with the F# compiler service, and the frontend needs the compiler service for type checking. A second parser would give a second set of ranges.

If code is adapted from these projects, and not only its design, the source file names its origin and the NOTICE file gives the attribution.

1.1 - F# frontend coverage

The F# constructs that the F# frontend refuses, with their diagnostic codes and examples.

The F# frontend compiles a subset of F#. Before it lowers a document, admission checks the syntax and refuses each construct outside the subset with an error diagnostic. All refusals of a run are reported together, and the frontend writes no model. The overview describes the supported subset.

This page is generated from the admission catalog of the frontend, so it lists every code that admission can report. Each rule has a minimal example that gives only its own code. The conformance corpus has one refusal case for each code. Codes are stable once released.

Codes

CodeConstruct
fsharp.unsupported_mutablemutable binding or field
fsharp.unsupported_assignmentassignment
fsharp.unsupported_looploop
fsharp.unsupported_classclass
fsharp.unsupported_interfaceinterface
fsharp.unsupported_membermember
fsharp.unsupported_object_expressionobject expression
fsharp.unsupported_sequence_expressionsequence expression
fsharp.unsupported_computation_expressioncomputation expression
fsharp.unsupported_comprehensionlist comprehension or range
fsharp.unsupported_arrayarray
fsharp.unsupported_exceptionexception
fsharp.unsupported_active_patternactive pattern
fsharp.unsupported_unit_of_measureunit of measure
fsharp.unsupported_quotationquotation
fsharp.unsupported_anonymous_recordanonymous record
fsharp.unsupported_structstruct type or struct tuple
fsharp.unsupported_inlineinline function
fsharp.unsupported_attributeattribute with an effect
fsharp.unsupported_type_testtype test
fsharp.unsupported_castcast
fsharp.unsupported_nullnull
fsharp.unsupported_useuse binding
fsharp.unsupported_sequentialsequential expression
fsharp.unsupported_literalliteral of an unsupported type
fsharp.unsupported_enumenum
fsharp.unsupported_delegatedelegate
fsharp.unsupported_pattern_guardpattern guard
fsharp.unsupported_or_patternor pattern
fsharp.unsupported_record_patternrecord pattern
fsharp.unsupported_list_patternlist pattern with elements
fsharp.unsupported_named_field_patternnamed field pattern
fsharp.unsupported_conditional_compilationconditional compilation
fsharp.unsupported_signature_filesignature file

The fallback rules below refuse any expression, pattern, type or declaration outside the supported subset that no specific rule refused.

CodeConstruct
fsharp.unsupported_expressionexpression outside the supported subset
fsharp.unsupported_patternpattern outside the supported subset
fsharp.unsupported_typetype outside the supported subset
fsharp.unsupported_declarationdeclaration outside the supported subset

Rules

fsharp.unsupported_mutable

Construct: mutable binding or field

Message: Mutable bindings and fields are not supported; Morphir values are immutable.

Suggestion: Use a new let binding, or a record update, instead of changing a value.

Example. The diagnostic is at mutable.

Example.fs:

namespace Acme.Demo
module Example =
    let mutable total = 0

fsharp.unsupported_assignment

Construct: assignment

Message: Assignment with ‘<-’ is not supported; Morphir values are immutable.

Suggestion: Return a new value instead of changing an existing one.

Example. The diagnostic is at builder.Length <- 0.

Example.fs:

namespace Acme.Demo
module Example =
    let reset (builder: System.Text.StringBuilder) = builder.Length <- 0

fsharp.unsupported_loop

Construct: loop

Message: Loops (‘for’, ‘while’) are not supported; Morphir has no statements.

Suggestion: Use recursion or a List function such as List.map or List.fold.

Example. The diagnostic is at for index in 1 .. 3 do ignore index.

Example.fs:

namespace Acme.Demo
module Example =
    let count () = for index in 1 .. 3 do ignore index

fsharp.unsupported_class

Construct: class

Message: Classes are not supported; Morphir types are records, unions and type abbreviations.

Suggestion: Use a record for the data and module functions for the behavior.

Example. The diagnostic is at this text:

Counter() =
        member _.Value = 1

Example.fs:

namespace Acme.Demo
module Example =
    type Counter() =
        member _.Value = 1

fsharp.unsupported_interface

Construct: interface

Message: Interfaces are not supported; Morphir has no subtyping.

Suggestion: Use a record of functions, or a union of the cases.

Example. The diagnostic is at this text:

IShape =
        abstract Area: float

Example.fs:

namespace Acme.Demo
module Example =
    type IShape =
        abstract Area: float

fsharp.unsupported_member

Construct: member

Message: Members and type extensions are not supported; Morphir types have no methods.

Suggestion: Define a module function that takes the value as a parameter.

Example. The diagnostic is at member this.Double = this.Seats * 2.

Example.fs:

namespace Acme.Demo
module Example =
    type Car = { Seats: int } with
        member this.Double = this.Seats * 2

fsharp.unsupported_object_expression

Construct: object expression

Message: Object expressions are not supported; Morphir has no objects.

Suggestion: Use a record of functions.

Example. The diagnostic is at { new System.Object() with member _.ToString() = "x" }.

Example.fs:

namespace Acme.Demo
module Example =
    let text = { new System.Object() with member _.ToString() = "x" }

fsharp.unsupported_sequence_expression

Construct: sequence expression

Message: Sequence expressions (‘seq { }’) are not supported; Morphir has no lazy sequences.

Suggestion: Use a list and the List functions.

Example. The diagnostic is at { yield 1 }.

Example.fs:

namespace Acme.Demo
module Example =
    let numbers = seq { yield 1 }

fsharp.unsupported_computation_expression

Construct: computation expression

Message: Computation expressions are not supported; Morphir has no effects or builders.

Suggestion: Write the steps as plain functions, for example with Result.bind.

Example. The diagnostic is at { return 1 }.

Example.fs:

namespace Acme.Demo
module Example =
    let work = async { return 1 }

fsharp.unsupported_comprehension

Construct: list comprehension or range

Message: List comprehensions and ranges (’[ for … ]’, ‘[ 1 .. 3 ]’) are not supported.

Suggestion: Use List.map, List.filter or an explicit list.

Example. The diagnostic is at [ for x in [ 1; 2 ] -> x * x ].

Example.fs:

namespace Acme.Demo
module Example =
    let squares = [ for x in [ 1; 2 ] -> x * x ]

fsharp.unsupported_array

Construct: array

Message: Arrays are not supported; Morphir collections are immutable lists.

Suggestion: Use a list.

Example. The diagnostic is at [| 1; 2 |].

Example.fs:

namespace Acme.Demo
module Example =
    let numbers = [| 1; 2 |]

fsharp.unsupported_exception

Construct: exception

Message: Exceptions (’exception’, ’try … with’, ’try … finally’) are not supported; Morphir has no effects.

Suggestion: Return a Result or an option.

Example. The diagnostic is at exception Failed of string.

Example.fs:

namespace Acme.Demo
module Example =
    exception Failed of string

fsharp.unsupported_active_pattern

Construct: active pattern

Message: Active patterns are not supported; Morphir patterns match only constructors and values.

Suggestion: Use a union and a function that returns it.

Example. The diagnostic is at let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd.

Example.fs:

namespace Acme.Demo
module Example =
    let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd

fsharp.unsupported_unit_of_measure

Construct: unit of measure

Message: Units of measure are not supported; Morphir numbers carry no unit.

Suggestion: Use a single-case union to name the quantity.

Example. The diagnostic is at [<Measure>] type kg.

Example.fs:

namespace Acme.Demo
module Example =
    [<Measure>] type kg

fsharp.unsupported_quotation

Construct: quotation

Message: Quotations are not supported; Morphir models values, not code.

Example. The diagnostic is at <@ 1 + 1 @>.

Example.fs:

namespace Acme.Demo
module Example =
    let expression = <@ 1 + 1 @>

fsharp.unsupported_anonymous_record

Construct: anonymous record

Message: Anonymous records are not supported; Morphir record types have names.

Suggestion: Declare a record type.

Example. The diagnostic is at {| X = 1 |}.

Example.fs:

namespace Acme.Demo
module Example =
    let point = {| X = 1 |}

fsharp.unsupported_struct

Construct: struct type or struct tuple

Message: Struct types and struct tuples are not supported; Morphir has no value-type layout.

Suggestion: Remove [] or ‘struct’.

Example. The diagnostic is at struct (1, 2).

Example.fs:

namespace Acme.Demo
module Example =
    let pair = struct (1, 2)

fsharp.unsupported_inline

Construct: inline function

Message: Inline functions are not supported; Morphir functions are not specialized at their call sites.

Suggestion: Remove ‘inline’ and give the parameters concrete types.

Example. The diagnostic is at inline.

Example.fs:

namespace Acme.Demo
module Example =
    let inline double (x: int) = x * 2

fsharp.unsupported_attribute

Construct: attribute with an effect

Message: This attribute changes the meaning of the code and is not supported.

Suggestion: Remove the attribute. Only RequireQualifiedAccess, AutoOpen, CompilationRepresentation, CompiledName and MorphirName are accepted.

Example. The diagnostic is at Literal.

Example.fs:

namespace Acme.Demo
module Example =
    [<Literal>]
    let Limit = 10

fsharp.unsupported_type_test

Construct: type test

Message: Type tests (’:?’) are not supported; Morphir has no runtime types.

Suggestion: Use a union to tell the cases apart.

Example. The diagnostic is at value :? string.

Example.fs:

namespace Acme.Demo
module Example =
    let isText (value: obj) = value :? string

fsharp.unsupported_cast

Construct: cast

Message: Casts (’:>’, ‘:?>’, ‘upcast’, ‘downcast’) are not supported; Morphir has no subtyping.

Example. The diagnostic is at 1 :> obj.

Example.fs:

namespace Acme.Demo
module Example =
    let boxed = (1 :> obj)

fsharp.unsupported_null

Construct: null

Message: ’null’ is not supported; Morphir values are never null.

Suggestion: Use an option.

Example. The diagnostic is at null.

Example.fs:

namespace Acme.Demo
module Example =
    let nothing: string = null

fsharp.unsupported_use

Construct: use binding

Message: ‘use’ bindings are not supported; Morphir has no resources to dispose.

Suggestion: Use ’let’.

Example. The diagnostic is at this text:

use stream = new System.IO.MemoryStream()
        stream.Length

Example.fs:

namespace Acme.Demo
module Example =
    let size () =
        use stream = new System.IO.MemoryStream()
        stream.Length

fsharp.unsupported_sequential

Construct: sequential expression

Message: Sequential expressions (‘a; b’) are not supported; Morphir values have no side effects.

Suggestion: Remove the expression whose value is discarded.

Example. The diagnostic is at this text:

ignore 1
        2

Example.fs:

namespace Acme.Demo
module Example =
    let both () =
        ignore 1
        2

fsharp.unsupported_literal

Construct: literal of an unsupported type

Message: This literal type is not supported; Morphir numbers are Int, Float and Decimal.

Suggestion: Use an int, int64, float or decimal literal.

Example. The diagnostic is at 1uy.

Example.fs:

namespace Acme.Demo
module Example =
    let small = 1uy

fsharp.unsupported_enum

Construct: enum

Message: Enums are not supported; Morphir custom types have no numeric values.

Suggestion: Use a union without fields.

Example. The diagnostic is at this text:

Color =
        | Red = 0
        | Blue = 1

Example.fs:

namespace Acme.Demo
module Example =
    type Color =
        | Red = 0
        | Blue = 1

fsharp.unsupported_delegate

Construct: delegate

Message: Delegates are not supported; Morphir has function types.

Suggestion: Use a function type.

Example. The diagnostic is at Handler = delegate of int -> unit.

Example.fs:

namespace Acme.Demo
module Example =
    type Handler = delegate of int -> unit

fsharp.unsupported_pattern_guard

Construct: pattern guard

Message: Pattern guards (‘when’) are not supported; a Morphir pattern has no condition.

Suggestion: Match the case, then use ‘if’ in its body.

Example. The diagnostic is at n > 3.

Example.fs:

namespace Acme.Demo
module Example =
    let size n =
        match n with
        | n when n > 3 -> "big"
        | _ -> "small"

fsharp.unsupported_or_pattern

Construct: or pattern

Message: Or patterns (‘A | B’) are not supported; a Morphir case has one pattern.

Suggestion: Write one case for each alternative.

Example. The diagnostic is at 1 | 2.

Example.fs:

namespace Acme.Demo
module Example =
    let low n =
        match n with
        | 1 | 2 -> true
        | _ -> false

fsharp.unsupported_record_pattern

Construct: record pattern

Message: Record patterns are not supported.

Suggestion: Bind the record to a variable and read its fields.

Example. The diagnostic is at { Seats = s }.

Example.fs:

namespace Acme.Demo
module Example =
    type Car = { Seats: int }
    let seats car =
        match car with
        | { Seats = s } -> s

fsharp.unsupported_list_pattern

Construct: list pattern with elements

Message: List patterns with elements (’[ a; b ]’) are not supported.

Suggestion: Use ‘::’ and ‘[]’ patterns.

Example. The diagnostic is at [ a ].

Example.fs:

namespace Acme.Demo
module Example =
    let first xs =
        match xs with
        | [ a ] -> a
        | _ -> 0

fsharp.unsupported_named_field_pattern

Construct: named field pattern

Message: Named field patterns (‘Case(field = p)’) are not supported.

Suggestion: Match the fields by position.

Example. The diagnostic is at Rented(days = d).

Example.fs:

namespace Acme.Demo
module Example =
    type Status = Rented of days: int
    let days status =
        match status with
        | Rented(days = d) -> d

fsharp.unsupported_conditional_compilation

Construct: conditional compilation

Message: Conditional compilation (’#if’) is not supported, because this frontend does not choose defines.

Suggestion: Remove the directive, or keep one branch.

Example. The diagnostic is at #if DEBUG.

Example.fs:

namespace Acme.Demo
module Example =
#if DEBUG
    let mode = "debug"
#else
    let mode = "release"
#endif

fsharp.unsupported_signature_file

Construct: signature file

Message: Signature files (’.fsi’) are not supported; access comes from F# visibility and the exposed modules.

Suggestion: Remove the signature file and use ‘private’ or ‘internal’.

Example. The diagnostic is an empty range at the start of the first document.

Example.fsi:

namespace Acme.Demo
module Example =
    val total: int

Example.fs:

namespace Acme.Demo
module Example =
    let total = 1

fsharp.unsupported_expression

Construct: expression outside the supported subset

Message: This kind of expression is not supported by the F# frontend.

Example. The diagnostic is at lazy 1.

Example.fs:

namespace Acme.Demo
module Example =
    let later = lazy 1

fsharp.unsupported_pattern

Construct: pattern outside the supported subset

Message: This kind of pattern is not supported by the F# frontend.

Example. The diagnostic is at (a, _) & (_, b).

Example.fs:

namespace Acme.Demo
module Example =
    let both pair =
        match pair with
        | (a, _) & (_, b) -> a + b

fsharp.unsupported_type

Construct: type outside the supported subset

Message: This kind of type is not supported by the F# frontend.

Example. The diagnostic is at #seq<int>.

Example.fs:

namespace Acme.Demo
module Example =
    let total (values: #seq<int>) = Seq.sum values

fsharp.unsupported_declaration

Construct: declaration outside the supported subset

Message: This kind of declaration is not supported by the F# frontend.

Example. The diagnostic is at do ignore 1.

Example.fs:

namespace Acme.Demo
module Example =
    do ignore 1