F# Compiler Guide


Representation Decisions in the F# Compiler

Consider the following declarations, all of which look very similar.

module M = 
    let z = 1                   
    let f = x + z               
                                

type C(w: int, z: int) =        
                                
    let f x = x + z             
    let f x = f 3 + x           
                                

let g (z: int) =                
    let f x = x + 1             

Part of the job of the F# compiler is to "decide" how these declarations are compiled. The following acts as a guide to how these different bindings are represented and where these decisions are made.

First for module-level let bindings. These representations are decided by code in CheckExpressions.fs and CheckDeclarations.fs based on syntax.

module M = 
    let z = 1              // z --> static property + field, required by spec, compiled name mandated
    let f x = x + z          // f --> static method, required by spec, compiled name mandated

Next for class-level let bindings. These representations are decided by code in CheckIncrementalClasses.fs based on analysis of use.

//    Decided in CheckIncrementalClasses.fs based on analysis of use
type C(w: int, z: int) =   // w --> local to object constructor, required by spec
                           // z --> private instance field, required by spec
    let f x = x + z        // f --> private instance method, required by spec, compiled name not mandated
                           // Note: initially uses an ephemeral 'f' Val then creates a member Val with compiled name
                           
    let f x = f 3 + x      // f --> private instance method, required by spec, compiled name not mandated
                           // Note: initially uses an ephemeral 'f' Val then creates a member Val with compiled name
                           
    static let g x = x + 1 // g --> private static method, required by spec, compiled name not mandated, initially uses an ephemeral 'g' Val then creates a member Val with compiled name
    
    static let g x = g 3   // g --> private static method, required by spec, compiled name not mandated, initially uses an ephemeral 'g' Val then creates a member Val with compiled name

Next for expression-level let bindings. These representations are decided by code in various optimization phases.

let g (z: int) =          // z --> local + field in closure for 'f', not mandated
    let f x = x + 1       // f --> FSharpFunc value, or maybe a static method, not mandated 
                          //    Decided in various optimization phases

NOTE: The representation decision is implied by the addition of ValReprInfo to the Val node.

module M from representations
val z: int
val f: int
type C = new: w: int * z: int -> C
val w: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>
<example id="int-example"><code lang="fsharp"></code></example>


--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>


--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
val f: (int -> int)
val x: int
val g: z: int -> 'a
Multiple items
type C = new: w: int * z: int -> C

--------------------
new: w: int * z: int -> C