F# Compiler Technical Overview

Please help improve this guide by editing it and submitting a pull-request. If you have specific topics that you would like to see addressed in this guide, please add an issue.

Contents

Introduction

This guide discusses the F# Compiler source code and implementation from a technical point of view.

See also Contributing to the F# Language, Compiler and Core Library.

This guide can be read in conjunction with any or all of:

These all include the same core code, and the relationship between them is described here.

Overview

The F# compiler repositories are used to produce a range of different artifacts. For the purposes of this guide, the important ones are:

The FSharp.Compiler.Service NuGet package and dll, shipped as an integrated component in most F# editor and scripting tools.

fsc.exe , fsi.exe and FSharp.Compiler.dll , the core binaries of the Visual F# tools for Windows, by Microsoft. These tools also include FSharp.LanguageService.Compiler.dll , a special trimmed-down build of the core of the F# compiler.

fsharpc , fsharpi , fsc.exe , fsi.exe , FSharp.Compiler.dll , the core binaries of the cross-platform open edition packages for F#, included in both Linux packages and Xamarin tooling for F#.

FSharp.Core.dll (see the separate guide on versions and related matters) is not covered in this guide except where there is an interaction with the F# compiler core.

In all these cases these distributions of F# include the core of the F# compiler:

The fsharp directory contains the core compiler logic

The absil directory contains the Abstract IL library which the F# compiler uses

Key Data Formats and Representations

The following are the key data formats and internal data representations of the F# compiler code in its various configurations:

Key Compiler Phases

Below, you can see a diagram of how different phases of F# compiler work:

The following are the key phases and high-level logical operations of the F# compiler code in its various configurations:

Basic lexing. Produces a token stream from input source file text.

White-space sensitive lexing. Accepts and produces a token stream, augmenting per the F# Language Specification.

Parsing. Accepts a token stream and produces an AST per the grammar in the F# Language Specification.

Resolving references. See ReferenceResolution.fsi/ReferenceResolution.fs.

Accepts command-line arguments and produces information about assembly references. Uses MSBuild for some references, only really used in F# Interactive.

Importing referenced .NET binaries, see import.fsi/ import.fs. Accepts file references and produces a TAST node for each referenced assembly, including information about its type definitions (and type forwarders if any).

Importing referenced F# binaries and optimization information as TAST data structures, see pickle.fs/fsi. Accepts binary data and produces TAST nodes for each referenced assembly, including information about its type/module/function/member definitions.

Sequentially type checking files, see TypeChecker.fsi/ TypeChecker.fs. Accepts an AST plus a type checking context/state and produces new TAST nodes incorporated into an updated type checking state, plus additional TAST Expression nodes used during code generation.

Pattern match compilation, see PatternMatchCompilation.fsi/PatternMatchCompilation.fs. Accepts a subset of checked TAST nodes representing F# pattern matching and produces TAST expressions implementing the pattern matching. Called during type checking as each construct involving pattern matching is processed.

Constraint solving, see ConstraintSolver.fsi/ ConstraintSolver.fs. A constraint solver state is maintained during type checking of a single file, and constraints are progressively asserted (i.e. added to this state). Fresh inference variables are generated and variables are eliminated (solved). Variables are also generalized at various language constructs, or explicitly declared, making them “rigid”. Called during type checking as each construct is processed.

Post-inference type checks, see PostInferenceChecks.fsi/ PostInferenceChecks.fs. Called at the end of type checking/inference for each file. A range of checks that can only be enforced after type checking on a file is complete.

Quotation translation, see QuotationTranslator.fsi/fs/QuotationPickler.fs/fs. Generates the stored information for F# quotation nodes, generated from the TAST expression structures of the F# compiler. Quotations are ultimately stored as binary data plus some added type references. “ReflectedDefinition” quotations are collected and stored in a single blob.

Optimization phases, primarily the “Optimize” (peephole/inlining) and “Top Level Representation” (lambda lifting) phases, see Optimizer.fsi/Optimizer.fs and InnerLambdasToTopLevelFuncs.fsi/fs and LowerCallsAndSeqs.fs. Each of these takes TAST nodes for types and expressions and either modifies the nodes in place or produces new TAST nodes.

These phases are orchestrated in CompileOptions.fs

Code generation, see IlxGen.fsi/IlxGen.fs Accepts TAST nodes and produces Abstract IL nodes.

Abstract IL code rewriting, see EraseClosures.fs and EraseUnions.fs. Eliminates some constructs by rewriting Abstract IL nodes.

Binary emit (used by the F# Compiler fsc.exe), see ilwrite.fsi/ilwrite.fs.

Reflection-Emit (used by F# Interactive fsi.exe), see ilreflect.fs.

The above are the internal phases and transformations used to build the following:

Coding Standards and Idioms

Abbreviations

The compiler codebase uses various abbreviations. These aren’t necessarily good standards but they are what’s used. Here are some of them:

Abbreviation Meaning ad Accessor domain, meaning the permissions the accessing code has to access other constructs amap Assembly map, saying how to map IL references to F# CCUs arg Argument (parameter) argty Argument (parameter) type arginfo Argument (parameter) metadata ccu Reference to an F# compilation unit = an F# DLL (possibly including the DLL being compiled) celem Custom attribute element cenv Compilation environment. Means different things in different contexts, but usually a parameter for a singlecompilation state object being passed through a set of related functions in a single phase. The compilation state is often mutable. cpath Compilation path, meaning A.B.C for the overall names containing a type or module definition css Constraint solver state. denv Display Environment. Parameters guiding the formatting of types einfo An info object for an event (whether a .NET event, an F# event or a provided event) e Expression env Environment. Means different things in different contexts, but usually immutable state being passed and adjusted through a set of related functions in a single phase. finfo An info object for a field (whether a .NET field or a provided field) fref A reference to an ILFieldRef Abstract IL node for a field reference. Would normally be modernized to ilFieldRef g The TcGlobals value id Identifier lid Long Identifier m A source code range marker mimpl IL interface method implementation minfo An info object for a method (whet a .NET method, an F# method or a provided method) modul a TAST structure for a namespace or F# module pat Pattern, a syntactic AST node representing part of a pattern in a pattern match pinfo An info object for a property (whether a .NET property, an F# property or a provided property) rfref Record or class field reference, a reference to a TAST node for a record or class field scoref The scope of a reference in IL metadata, either assembly, .netmodule or local spat Simple Pattern, a syntactic AST node representing part of a pattern in a pattern match tau A type with the “forall” nodes stripped off (i.e. the nodes which represent generic type parameters). Comes from the notation 𝛕used in type theory tcref Type constructor reference (an EntityRef) tinst Type instantiation tpenv Type parameter environment, tracks the type parameters in scope during type checking ty , typ Type, usually a TAST type tys , typs List of types, usually TAST types typar Type Parameter tyvar Type Variable, usually referring to an IL type variable, the compiled form of an F# type parameter ucref Union case reference, a reference to a TAST node for a union case vref Value reference, a reference to a TAST node for a value

Phase Abbreviation Meaning Syn Abstract Syntax Tree Tc Type-checker IL Abstract IL = F# representation of .NET IL Ilx Extended Abstract IL = .NET IL plus a coulpe of contructs that get erased

Error Messages and User-facing Text

Adding Error Messages

Steffen Forkmann has written an excellent introductory guide to adding a new error message.

Formatting User Text from TAST items

When formatting TAST objects such as TyconRef s as text, you normally use either

The functions in the NicePrint module such as NicePrint.outputTyconRef . These take a DisplayEnv that records the context in which a type was referenced, e.g. the open namespaces. Opened namespaces are not shown in the displayed output.

The DisplayName properties on the relevant object. This drops the 'n text that .NET adds to the compiled name of a type, and uses the F#-facing name for a type rather than the compiled name for a type (e.g. the name given in a CompiledName attribute).

The functions such as Tastops.fullTextOfTyconRef , used to show the full, qualified name of an item.

When formatting “info” objects, see the functions in the NicePrint module.

Notes on displaying types

When displaying a type, you will normally want to “prettify” the type first (i.e. make the type “pretty”). This converts any remaining type inference variables to new, better user-friendly type variables with names like 'a . Various functions prettify types prior to display, e.g. NicePrint.layoutPrettifiedTypes and others.

When displaying multiple types in a comparative way, e.g. two types that didn’t match, you will want to display the minimal amount of infomation to convey the fact that the two types are different, e.g. NicePrint.minimalStringsOfTwoTypes .

When displaying a type, you have the option of displaying the constraints implied by any type variables mentioned in the types, appended as when ... . For example, NicePrint.layoutPrettifiedTypeAndConstraints .

Input Size Limitations

The F# compiler must accept large inputs such as

large array expressions

large list expressions

long lists of sequential expressions

long lists of let v1 = e1 in let v2 = e2 in ....

long sequences of if .. then ... else expressions

expressions long sequences of match x with ... | ... expressions

expressions combinations of these

The general problem is that the input sizes accepted in various dimensions are determined partly by available process stack for the devenv.exe, fsc.exe, fsi.exe and fsiAnyCpu.exe processes. The input size limitations are not precisely specified nor do we test to precise numbers and fail above those numbers). Instead historically we’ve been able to remove these limits when we’ve encountered them by moving more stack to the heap for certain operations (e.g. collecting free variables down long chains of let ) through standard continuation coding techniques in the compiler.

In many dimensions of expansion, large expressions simply don’t occur - for example you don’t get lambdas nested 20,000 deep (you might get 100 deep at most). However, the above cases are examples where large expressions do occur in practice.

Asides from array expressions, most of the above are called “linear” expressions in that there is a single linear hole in the shape of expressions, e.g. to be more precise about where these linear holes are:

expr :: HOLE (list expressions or other right-linear constructions)

(list expressions or other right-linear constructions) expr; HOLE (sequential expressions)

(sequential expressions) let v = expr in HOLE (let expressions)

(let expressions) if expr then expr else HOLE (conditional expression)

(conditional expression) match expr with pat[vs] -> e1[vs] | pat2 -> HOLE (e.g. match expr with Some x -> ... | None -> ... )

Note application expressions are not actually in this list.

Processing these in the naive way often uses unbounded stack. Instead, these should be processed using continuations that place the stack frames on the heap. For example, the remapExpr operation becomes two functions, remapExpr (for non-linear cases) and remapLinearExpr (for linear cases). The latter tailcalls for constructs in the HOLE positions mentioned above, passing the result to the continuation.

and remapLinearExpr g compgen tmenv expr contf = match expr with | Expr.Let (bind, bodyExpr, m, _) -> ... // tailcall for the linear position remapLinearExpr g compgen tmenvinner bodyExpr (contf << (fun bodyExpr' -> ...)) | Expr.Sequential (expr1, expr2, dir, spSeq, m) -> ... // tailcall for the linear position remapLinearExpr g compgen tmenv expr2 (contf << (fun expr2' -> ...)) | LinearMatchExpr (spBind, exprm, dtree, tg1, expr2, sp2, m2, ty) -> ... // tailcall for the linear position remapLinearExpr g compgen tmenv expr2 (contf << (fun expr2' -> ...)) | LinearOpExpr (op, tyargs, argsFront, argLast, m) -> ... // tailcall for the linear position remapLinearExpr g compgen tmenv argLast (contf << (fun argLast' -> ...)) | _ -> contf (remapExpr g compgen tmenv e) and remapExpr (g: TcGlobals) (compgen:ValCopyFlag) (tmenv:Remap) expr = match expr with ... | LinearOpExpr _ | LinearMatchExpr _ | Expr.Sequential _ | Expr.Let _ -> remapLinearExpr g compgen tmenv expr (fun x -> x)

Note

the tell-tale use of contf (continuation function)

(continuation function) the processing of the body expression e of a let-expression is tail-recursive, if the next construct is also a let-expression.

of a let-expression is tail-recursive, if the next construct is also a let-expression. the processing of the e2 expression of a sequential-expression is tail-recursive

expression of a sequential-expression is tail-recursive the processing of the second expression in a cons is tail-recursive

The code above may be considered incomplete, because arbitrary combinations of let and sequential expressions aren’t going to be dealt with in a tail-recursive way. We generally try to do these combinations as well.

Type Providers

The FSharp.TypeProvider.SDK project is now the canonical home for information on type providers.

Performance

Code Optimizations

The optimizations are performed in Optimizer.fs , Detuple.fs , InnerLambdasToTopLevelFuncs.fs and LowerCallsAndSeqs.fs .

The optimizations in Optimizer.fs are:

propagation of known values (constants, x = y, lambdas, tuples/records/union-cases of known values)

inlining of known lambda values

eliminating unused bindings

eliminating sequential code when there is no side-effect

eliminating switches when we determine definite success or failure of pattern matching

eliminating getting fields from an immutable record/tuple/union-case of known value

expand tuple bindings “let v = (x1,…x3)” to avoid allocations if it’s not used as a first class value

propogating cutting big functions into multiple methods, especially at match cases, to avoid massive methods that take a long time to JIT

removing tailcalls when it is determined that no code in the transitive closure does a tailcall nor recurses

How to debug optimized assembly for generated code

To look at the optimized assembly generated by the JIT, in Visual Studio:

Go to Debug -> Options… -> Debugging -> General

Uncheck “Enable Just My Code” and “Suppress JIT optimization on module load (Managed only)”

Set a breakpoint in your code and open the Disassembly window

Start debugging as normal.

Optimized code can be difficult to step through. Your breakpoint may never hit despite the “line” being executed, because the line doesn’t reliably map to generated code. You may need to re-arrange your code and put the breakpoint somewhere else. For example, put the breakpoint in a calling function and mark the function you want to inspect as inline so its code is inlined in the calling function.

Potential future optimizations: Better Inlining

From this user voice request: It would be great if the compiler can inline away CPS compositions like the following.

let inline f k = (fun x -> k (x + 1)) let inline g k = (fun x -> k (x + 2)) (f << g) id 1 // 4

That thread includes the insightful comment:

The problem I’ve described in one of the links Jack has linked to is actually more basic than the OP’s. It’s that the first order function passed as an argument to a second order function is not inlined even if both the second and first order functions are marked as inline. This makes it so that using second order functions always comes with a performance penalty, which is not the case for first order functions.

Compiler Startup Performance

Compiler startup performance is a key factor affecting happiness of F# users. If the compiler took 10sec to start up, then far fewer people would use F#.

On all platforms, the following factors affect startup performance:

Time to load compiler binaries. This depends on the size of the generated binaries, whether they are pre-compiled (e.g. using NGEN), and the way the .NET implementation loads them.

Time to open referenced assemblies (e.g. mscorlib.dll , FSharp.Core.dll ) and analyze them for the types and namespaces defined. This depends particularly on whether this is correctly done in an on-demand way.

Time to process “open” declarations are the top of each file. Processing these declarations have been observed to take time in some cases of F# compilation.

Factors specific to the specific files being compiled.

On Windows, compiler startup performance tends to be greatly improved through the use of NGEN. NGEN is run on fsc.exe and fsi.exe for installations of the Visual F# Tools.

On Mono/Linux/OSX, compiler startup performance is less good but is not too bad. Some improvements can be achieved using AOT (Mono’s equivalent of NGEN).

Note: If you are building tools using the FSharp.Compiler.Service NuGet package, NGEN is not automatically run on that DLL, and the startup time of the tool you are building may be degraded. If possible, you should arrange for NGEN to be run when your tool is installed.

Compiler Memory Usage

Overall memory usage is a primary determinant of the usability of the F# compiler and instances of the F# compiler service. Overly high memory usage results in poor throughput (particularly due to increased GC times) and low user interface responsivity in tools such as Visual Studio or other editing environments.

Key scenarios for memory usage

Overall memory usage depends considerably on scenario,phase and configuration. Some key scenarios are:

Overall memory usage of an instance of Visual Studio or another editing environment when editing F# projects

Overall memory usage of the Visual F# Power Tools in Visual Studio when editing and refactoring F# projects

Memory usage and throughput of the F# compiler fsc.exe

Memory usage and throughput of the F# Interactive dynamic scripting compiler fsi.exe

Analyzing memory usage of the F# Compiler and instances of the F# Compiler Service can be done using tools such as the Visual Studio Managed Memory analysis. For example:

Analyzing compiler memory usage

To analyze memory usage of the Visual F# Tools in Visual Studio (with or without the Visual F# Power Tools)

Take a process minidump of the devenv.exe process in Task Manager Open that .dmp file in another instance of Visual Studio and click on “Debug Managed Memory”

You can also compare to a baseline generated without using the Visual F# Tools.

This buckets memory usage by type and lets you analyze the roots keeping those objects alive.

At the time of writing, these were some of the top types consuming managed memory were as follows - the percentages are approximate and depend on scenario. In some cases these have been bucketed:

Type Approx % Category Cause MemChannel ~20% TAST Abs/IL In-memory representations of referenced DLLs. “System” DLLs are read using a memory-mapped file. ByteFile + others ValData ~12% TAST per-value data, one object for each F# value declared (or imported in optimization expressions) + others EntityData ~12% TAST various types for per-type-or-module-or-namespace-definition data, for each F# type declared, or F# or .NET type imported TyconAugmentation PublicPath Lazy<ModuleOrNamespaceType> Func<ModuleOrNamespaceType> + others ILTypeDefs , ILTypeDef ~9% TAST/AbsIL various types and delayed thunks for reading IL type definitions from .NET assemblies Tuple<ILScopeRef, ILAttributes, Lazy<ILTypeDef>> Lazy<Tuple<ILScopeRef, ILAttributes, Lazy<ILTypeDef>> Func<Tuple<ILScopeRef, ILAttributes, Lazy<ILTypeDef>> Import+lazyModuleOrNamespaceTypeForNestedTypes@400 + others MapNode<string,Item> ~10% TAST/NameResolution (in editor) incremental TcState and name resolution environments stored for various open files and intermediate file points in an active Visual Studio sesson MapNode<string,EntityRef> CapturedNameResolution NameResolutionEnv + others Lazy<FSharpList<ILAttribute>> ~6% TAST/AbsIL various types for delayed thunks for reading lists of attributes about .NET assemblies ILBinaryReader+seekReadCustomAttrs@2627 Func<FSharpList<ILAttribute>> Attrib + others Dictionary<Int32, String> ~3% TAST/AbsIL various tables including those used in reading binaries Dictionary<String, String> ~2% TAST/AbsIL memoization tables for strings EntityRef ~3% TAST nodes representing references to entities, pointing to an EntityData NonLocalEntityRef + others SynExpr.App ~3% AST nodes for untyped syntax tree, kept by editor environment + others ILMethodDefs , ILMethodDef ~2% TAST/AbsIL various types for reading IL methode definitions from .NET assemblies + others Ident ~1.5% TAST identifiers - a range and a reference to a string TType_fun ~1.5% TAST node for function types, especially in imported metadata TType_app ~1.5% TAST node for constructed types like list<int> FSharpList<TType> ~1.5% TAST lists of types, usually in tuple and type applications TyparData ~1.5% TAST data about type inference variables and generic parameters ValLinkagePartialKey ~1% TAST data indicating how one assembly references a value/method/member in another ILTypeRef ~1% TAST/AbsIL type references in AbstractIL metadata XmlDoc ~1% AST documentation strings + a long tail of other types, most of which probably belong in the above general categories, increasing the totals to 100%

Looking at the above analysis, the conclusions at the time of writing are

A considerable chunk of memory is used for in-memory copies of some referenced DLLs. In practice, this is difficult to avoid, as similar memory is also used even if using memory-mapped DLLs. Nearly all long-lived memory is related to the TAST nodes of the F# compiler data structure, or the related Abstract IL nodes for .NET assemblies being imported. The Abstract IL data structures for delayed-reading of .NET type definitions use memory inefficiently The Abstract IL data structures for delayed-reading of .NET attributes use memory relatively inefficiently There is a considerable “long tail” of memory usage when categorized by type

An alternative way to look at the data is at which F# types are being used inefficiently in long-stored objects. For example:

F# Core Type Approx % FSharpList<...> ~4% FSharpOption<...> ~1.5% Tuple<...> ~1.5% FSharpMap<...> ~1% FSharpSet<...> ~0% (negligible)

There are micro savings available here if you hunt carefully.

Compiler Throughput Performance

TBD - discuss topics related to compiler performance including phase costs, data structures, GC settings etc.

From Compiler to Language Service

Cross-project references

The compiler is generally built to compile one assembly: the assumption that we’re compiling one assembly is baked into several aspects of the design of the TAST.

In contract, FCS supports compiling a graph of projects, each for a different assembly. The TAST nodes are not shared between different project compilations. (ILModuleReader are shared by artificially extending their lifetime via caches).

How does this work?

The RawFSharpAssemblyData is the data blob that would normally be stuffed in the F# resource in the generated DLL in a normal compilation. That’s the “output” of checking each project.

This is used as “input” for the assembly reference of each consuming project (instead of an on-disk DLL)

Within each consuming project that blob is then resurrected to TAST nodes via TastPickle.fs.

Could we share? The thing is, I have no idea how to share these nodes - either from a lifetime point of view nor from a correctness point of view.

Re correctness: the process of generating this blob (TastPickle p_XYZ ) and resurrecting it (TastPickle u_* ) does some transformations to the TAST that are necessary for correctness of compilation, e.g. https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/TastPickle.fs#L737. So basically the TAST nodes from the compilation of one assembly are not valid when compiling a different assembly. Indeed it’s much worse than this - the TAST nodes include CcuData nodes which have access to a number of callbacks into the TcImports compilation context for the assembly being compiled, e.g. https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/tast.fs#L4156. So Tast nodes are “tied to a particular compilation of a particular assembly”. I don’t think there’s any way we can share these as a result without a lot of hard work digging out these assumption. Note that pretty much all the TAST nodes for an assembly compilation are tied together in a graph.

Re lifetime: the TAST nodes are tied together in a graph, so sharing one or two of them might drag across the entire graph and extend lifetimes of that graph. That is risky.

To reiterate: the compiler is built to compile an assembly, and the assumption that we’re compiling one assembly is baked into several aspects of the design of the TAST.

eventually Computations

Some parts of the F# codebase (specifically, the type checker) are written using eventually computation expressions, see EventuallyBuilder.

These define resumption-like computations which can be time-sliced, suspended or discarded at “bind” points.

This is done to ensure that long-running type-checking and other computations in the F# Compiler Service can be interrupted and cancelled. The documentation of the F# Compiler Service Operations Queue covers some aspects of this.

To clarify:

fsi.exe (F# Interactive) and fsc.exe don’t use time sliced computations – they force eventually computations synchronously without interruption.

Instances of the F# compiler service use time slicing for two things:

The low-priority computations of the reactor thread (i.e. the background typechecking of the incremental builder) The typechecking phase of TypeCheckOneFile which are high-priority computations on the reactor thread.

The first can be interrupted by having the incremental builder dependency graph (see IncrementalBuild.fsi/IncrementalBuild.fs) decide not to bother continuing with the computation (it drops it on the floor)

The second can be interrupted via having isResultObsolete to the F# Compiler Service API return true.

The F# Compiler Service Operations Queue

The F# Compiler Service has technical documentation on the operations queue used by the FSharpChecker component.

The F# Compiler Service Caches

The F# Compiler Service has technical documentation on the caches used by the FSharpChecker component.

Bootstrapping

The Microsoft/visualfsharp and fsharp/fsharp repositories are bootstrapped. That is, an existing F# compiler is used to build a “proto” compiler from the current source code. That “proto” compiler is then used to compile itself, producing a “final” compiler. This ensures the final compiler is compiled with all relevant optimizations and fixes.

The FSharp.Compiler.Service component is not bootstrapped and is simply compiled with an existing F# compiler to produce a .NET 4.x component. In practice this is sufficient given the overall stability of the codebases.

FSharp.Build

FSharp.Build.dll and Microsoft.FSharp.targets give XBuild/MSBuild support for F# projects ( .fsproj ). Although not strictly part of the F# compiler, these components are always found in F# tool distributions for Mono and Windows.

Also, Mono’s XBuild targets files Microsoft.Common.targets processes EmbeddedResource items differently to MSBuild and this can lead to differences in how things work.

The best test project for understanding what’s going on with resource names is tests\projects\Sample_VS2012_FSharp_ConsoleApp_net45_with_resource . This includes various EmbeddedResource items and some custom “FsSrGen” items that give rise to resources.

How F# handles EmbeddedResource items differently to C# (details)

F# differs subtly to C# in how EmbeddedResource items in .fsproj files are handled.

When the Sample_VS2012_FSharp_ConsoleApp_net45_with_resource EmbeddedResource specifications (minus the FsSrGen one) are used in a C# project we get roughly these command line arguments:

/resource:obj\Debug\ResxResource.resources /resource:obj\Debug\ResxResourceWithLogicalName.resources,The.Explicit.Name.Of.ResxResourceWithLogicalName /resource:obj\Debug\SubDir.ResxResourceInSubDir.resources /resource:obj\Debug\SubDir.ResxResourceWithLogicalNameInSubDir.resources,The.Explicit.Name.Of.ResxResourceWithLogicalNameInSubDir /resource:NonResxResourceWithLogicalName.txt,The.Explicit.Name.Of.NonResxResourceWithLogicalName /resource:NonResxResource.txt,NonResxResource.txt /resource:SubDir\NonResxResourceInSubDir.txt,SubDir.NonResxResourceInSubDir.txt /resource:SubDir\NonResxResourceWithLogicalNameInSubDir.txt,The.Explicit.Name.Of.NonResxResourceWithLogicalNameInSubDir

This gives a .NET Binary with these resource names:

.mresource public ResxResource.resources .mresource public The.Explicit.Name.Of.ResxResourceWithLogicalName .mresource public SubDir.ResxResourceInSubDir.resources .mresource public The.Explicit.Name.Of.ResxResourceWithLogicalNameInSubDir .mresource public The.Explicit.Name.Of.NonResxResourceWithLogicalName .mresource public NonResxResource.txt .mresource public SubDir.NonResxResourceInSubDir.txt .mresource public The.Explicit.Name.Of.NonResxResourceWithLogicalNameInSubDir

For F# on Windows using MSBuild we get these command line arguments:

--resource:obj\Debug\ResxResource.resources --resource:obj\Debug\ResxResourceWithLogicalName.resources --resource:obj\Debug\SubDir.ResxResourceInSubDir.resources --resource:obj\Debug\SubDir.ResxResourceWithLogicalNameInSubDir.resources --resource:NonResxResourceWithLogicalName.txt --resource:NonResxResource.txt --resource:SubDir\NonResxResourceInSubDir.txt --resource:SubDir\NonResxResourceWithLogicalNameInSubDir.txt --resource:obj\Debug\FSComp.resources

This gives a .NET Binary with these resource names:

.mresource public ResxResource.resources .mresource public ResxResourceWithLogicalName.resources .mresource public SubDir.ResxResourceInSubDir.resources .mresource public SubDir.ResxResourceWithLogicalNameInSubDir.resources .mresource public NonResxResourceWithLogicalName.txt .mresource public NonResxResource.txt .mresource public NonResxResourceInSubDir.txt .mresource public NonResxResourceWithLogicalNameInSubDir.txt .mresource public FSComp.resources

For F# on Linux/OSX using Mono and XBuild we get:

--resource:obj/Debug/ResxResource.resources --resource:obj/Debug/ResxResourceWithLogicalName.resources --resource:obj/Debug/ResxResourceInSubDir.resources --resource:obj/Debug/ResxResourceWithLogicalNameInSubDir.resources --resource:obj/Debug/FSComp.resources --resource:obj/Debug/FSCompLinkedInSuperDir.resources --resource:obj/Debug/FSCompLinkedInSameDir.resources --resource:obj/Debug/FSCompLinkedInSubDir.resources --resource:obj/Debug/NonResxResourceWithLogicalName.txt --resource:obj/Debug/NonResxResource.txt --resource:obj/Debug/NonResxResourceInSubDir.txt --resource:obj/Debug/NonResxResourceWithLogicalNameInSubDir.txt

This gives a .NET Binary with these resource names:

.mresource public ResxResource.resources .mresource public ResxResourceWithLogicalName.resources .mresource public ResxResourceInSubDir.resources .mresource public ResxResourceWithLogicalNameInSubDir.resources .mresource public FSComp.resources .mresource public FSCompLinkedInSuperDir.resources .mresource public FSCompLinkedInSameDir.resources .mresource public FSCompLinkedInSubDir.resources .mresource public NonResxResourceWithLogicalName.txt .mresource public NonResxResource.txt .mresource public NonResxResourceInSubDir.txt .mresource public NonResxResourceWithLogicalNameInSubDir.txt

Note

For both C# and F# on Windows ResX resources in subdirectories have SubDir prefixed. This is correct (C# also adds a default namespace, but the RootNamespace ` property is not set by default in F# projects - I’ve removed it from the C# project).

For F# on Windows, NonResX resources in subdirectories do not have SubDir prefixed. This deviates from C# Tracking Bug Issue. It’s unlikely we can change this at this point as it would be a breaking change but ideally it should be changed.

For F# on Linux, the resources are missing their subdirectory names in all cases. Tracking bug issue .mresource public SubDir.ResxResourceInSubDir.resources v. .mresource public ResxResourceInSubDir.resources

For F#, the explicit LogicalName property seems to be ignored even on Windows Tracking issue.

End Notes

Didn’t find what you want?

Please add an issue outlining what technical information you were looking for.

About Us

The F# Core Engineering Group is a working group associated with The F# Software Foundation.

Visit our website and please continue all the great work on core F# engineering!





First Published: 29 September 2015

The F# Core Engineering group