A directive is a line that starts with # (white space may come before it, and the # must be the first thing on the line for the ones the lexer reads). Some are handled by the lexer in every file (conditional compilation, line and warning directives), others only by F# Interactive and scripts (#r, #load and the rest).
Conditional compilation
#if DEBUG
let log msg = printfn "debug: %s" msg
#else
let log _ = ()
#endif
#if NET8_0_OR_GREATER || (WINDOWS && !LEGACY)
let modern = true
#elif NETSTANDARD2_0 // F# 11
let modern = false
#else
let modern = false
#endif
// comment.The expression after #if (and #elif, F# 11) is built from symbols, ! (not), &&, || and brackets, with the usual precedence. A symbol is true if it is defined by --define (or <DefineConstants> in the project) or by the build. There is no #define. Groups can nest; a group is #if, any number of #elif, at most one #else after them, and #endif; an #elif cannot follow #else. Indentation is not significant for these lines, and a block comment cannot share a line with one. In a part that is left out, strings and comments are not read specially, so a #endif inside a string of a skipped part still counts.
| Symbol | Defined when |
|---|---|
DEBUG, TRACE | the build configuration enables them (the Debug configuration defines DEBUG) |
COMPILED | the code is compiled with the F# compiler |
INTERACTIVE | the code runs in F# Interactive (dotnet fsi), as a session or a script |
NULLABLE | nullable reference types are on (<Nullable>enable</Nullable>, F# 9) |
NET6_0_OR_GREATER and the others of the SDK | the target framework matches; the SDK defines names such as NET8_0, NET8_0_OR_GREATER, NETSTANDARD2_0 and NETCOREAPP for the target of the project |
The viewer cannot know the defines, so it reads both sides of every #if as code, and colours the symbols in the condition as literals.
Warnings
#nowarn "25" "26" // turn warnings off, as strings
#nowarn 25 FS0026 // F# 9: numbers and the FS prefix, no quotes
#warnon 3390 // turn an optional warning on
#nowarnin a.fsor.fsifile turns the warning off in the lexical scope that follows; in a script it applies to the whole compilation.#warnonturns a warning on.- From F# 9 the numbers may be written without quotes and with or without the
FSprefix and leading zeros (#nowarn 57,0057,FS0057and the quoted forms all work). - F# 10 scopes them:
#nowarn 25…#warnon 25turns a warning off for the lines between. It also no longer allows multi-line and empty warning directives, white space between#andnowarn, or triple-quoted, interpolated or verbatim strings for the numbers.
Line directives and the rest of the lexer’s directives
#line 25 "file.fs",# 25 "file.fs",#line 25and the same with a verbatim string for the name: the following line is reported as that line (and file) in errors, debugging symbols and quotations. The directive applies to the line that immediately follows. It is meant for tools that generate F#.#lightand#light "on"(also#indent "on") are accepted and do nothing; the lexer reports#light "off"and#indent "off"as no longer supported (the specification describes#indent "off"as switching to verbose syntax; see file structure).- The first line of a file may be a
#!line, read as a comment, so a script can name its interpreter:#!/usr/bin/env dotnet fsi. - From F# 11 a line that starts with
#:is ignored by the compiler and left to a hosting tool (for example the .NET tooling for single-file programs, with lines like#:package Foo@1.0). The compiler does not implement what those lines mean. - A
#followed by an identifier at the start of a line that is none of these is passed on to the parser as a directive:# id stringforms are declarations in a module or namespace (#r,#loadand the others); what the compiler does with an unknown one was not checked (the grammar filepars.fsywas not read for it).
Script and F# Interactive directives
#r "nuget: Newtonsoft.Json, 13.0.3" // a NuGet package (F# 5)
#r "nuget: fsharp.data,usepackagetargets=true"
#r "paket: nuget FParsec" // an extension: paket
#r @"C:\libs\MyAssembly.dll" // an assembly on disk
#I @"C:\libs" // a search path for assemblies
#i "nuget: https://my-feed/index.json" // a package source
#load "Script1.fsx" "Helpers.fs" // other source files
#time "on"
#help
#version;; // F# 11
#quit
;;.| Directive | Meaning |
|---|---|
#r "nuget: …" | references a NuGet package (with an optional version after a comma, and options such as usepackagetargets=true) |
#r "extname: …" | references a package through a script extension, for example paket |
#r "name.dll" | references an assembly (the specification also has #reference as a long form) |
#I "path" | adds an assembly search path (the specification also has #Include) |
#i "nuget: source" | adds a package source for #r "nuget:" |
#load "file" | reads, compiles and runs one or more source files before the script; the files may be .fsx, .fs or .fsi |
#time, #time "on", #time "off" (F# 9: #time on) | toggles the display of elapsed real time, CPU time and garbage-collection counts after each interpreted section |
#help | shows the available directives; from F# 9 #help List.map shows the documentation of a function |
#quit, #q | ends the session (the short form is from the specification) |
#clear | clears the screen (not in the documentation read; from memory of fsi) |
#version | shows the version of the interactive session (F# 11) |
A script refers to its own place with __SOURCE_DIRECTORY__ and __SOURCE_FILE__ and gets the fsi object (command-line arguments in fsi.CommandLineArgs) from FSharp.Compiler.Interactive.Settings. See the tooling page for running scripts.
In the viewer
The directive name (#if, #r, #nowarn …) is coloured as a directive when it is the first thing on a line and is one of the names above; the arguments are read as ordinary code, so strings and numbers keep their colours. The symbols after #if and #elif are literals, and a // comment after them is a comment. A #! line at the first position of the file is a comment, and a #: line is a directive as a whole.
Not described
Known, not yet covered
- Reference: the compiler options that go with these directives (
--define,--langversion,--nowarn,--warnon,--warnaserror,--lib,--referenceincompiler-options.md), the full list of F# Interactive options, the exact grammar of#r "nuget:"options and#helptopics, and the complete list of symbols that the SDK defines were not read in full. - Reference: whether
#rand#loadaccept anything other than quoted strings in the current lexer (F# 9 allows non-string arguments for#nowarnand#timeonly, per its release page).
Unknown
- Reference:
#clearand the short#qare not in the current documentation or the lexer: the viewer colours them because F# Interactive is known to accept them; this was not verified.
See also
- File structure – scripts,
#loadorder and the entry point - Lexical structure – the other lexical forms