Skip to content

Conditional Compilation

m0rkeulv edited this page Oct 25, 2026 · 1 revision

Haxe code can use #if, #elseif, #else and #end to compile different code for different targets and settings. The IDE evaluates these conditions the way the compiler would for the build each file belongs to: branches the compiler would skip are shown dimmed, but they are still parsed, so you can keep editing them comfortably. This page explains which defines the IDE uses, what you see in the editor and how to switch the define set.

For the directives themselves, see the Haxe manual on conditional compilation.

class Main {
	static function main() {
		#if js
		trace("browser or Node.js");
		#elseif (cpp || hl)
		trace("native or HashLink build");
		#else
		#error "This target is not supported"
		#end

		#if (haxe_ver >= 4.3)
		trace("Haxe 4.3 or later");
		#end
	}
}

Which defines the IDE uses

Each Haxe file is evaluated against the defines of one build file: the build it is analyzed under, its resolve context. That is the build file whose class paths contain the file, so the #if blocks of a Node.js program's main class follow the Node.js build even while a browser build is active. A file of a haxelib library follows a build that uses the library. Where several build files are equal candidates, the active build file decides unless you pick another for the file. The defines come from these sources:

Source Defines
The build file Every -D name and -D name=value of an hxml file, and debug for -debug. A dashed name such as -D my-flag defines my_flag, as in the compiler. For an hxml file that chains several compilations with --next, only the selected section counts. For Lime, OpenFL and HXP project files, the -D defines the build passes to the compiler for the selected target, once the plugin has evaluated the file (see Build Files). For NME files, the defines of the evaluated project for the selected target. See Targets and Sections.
The build's target The defines of the Haxe target the build compiles to. See the table below.
The build's libraries One define per library, such as mylib=1.2.0, and the -D lines of the library's extraParams.hxml, as haxelib path reports them for the compiler, also for libraries pulled in by other libraries. They appear once the libraries have been resolved, which happens each time a trusted project opens; see Libraries and Haxelib.
The Haxe version haxe_ver and haxe, plus haxe3, haxe4 and haxe5 up to the major version (for a 4.x version: haxe3 and haxe4). By default the version is the module's language level; see The Haxe version.
The HashLink version hl_ver, read from the Haxe SDK's standard library, unless the build file sets it itself.
A custom target custom_target and target.name, when the build file has a Custom target under its IDE Overrides and selects no target of its own. See Defines and Custom Targets.
The build file's IDE Overrides Defines you add, change or remove for the build file in the Haxe tool window. They only affect the IDE and are never added to your builds. See Defines and Custom Targets.

A define without a value counts as 1, as with the compiler. A file that no build file compiles uses the active build file's defines. If no build file is active either, none of these defines are set for it, not even haxe_ver.

The target defines the IDE sets, the same as the compiler:

Target Defines sys static utf16
JavaScript js yes
HashLink hl yes yes yes
C++ cpp yes yes yes
Cppia cppia, cpp yes yes yes
Neko neko yes
Flash flash yes yes
PHP php yes
Java / JVM java, jvm yes yes yes
C# cs yes yes yes
Python python yes
Lua lua yes
Interpreter (--interp) eval, interp yes

Every target also sets target.name to its platform name, so #if (target.name == "js") works, and the matching target.sys, target.static and target.utf16, plus target.threaded, target.unicode and target.atomics where the platform has them.

For a Lime, OpenFL, NME or HXP project file, the Haxe target follows the selected target, for example js for HTML5 and cpp for the desktop and mobile targets.

The IDE analyzes your code as a normal compilation sees it, so macro is not defined and #if macro branches show as inactive.

Tip

A library's extraParams.hxml can also set defines with a --macro; for example, hxnodejs defines nodejs that way. Only the compiler runs such macros, so the IDE does not see those defines. Test for the library's own define instead (#if hxnodejs), or add the define under the build file's IDE Overrides. haxe --help-defines lists the compiler's built-in defines.

The Haxe version

#if (haxe_ver >= 4.1) and similar checks are evaluated against the language level of the module that contains the file's build file (see Core Concepts), not against the Haxe SDK. To use the SDK's real version instead:

  1. Open Settings | Build, Execution, Deployment | Compiler | Haxe Compiler.
  2. Clear Use language level for conditional compilation (haxe_ver).

The compilation server always uses its own version, whatever this option says. See Compilation Server.

What you see in the editor

Inactive branches

A branch whose condition is false is inactive. The IDE still parses it as Haxe code and shows its normal syntax colors, faded toward the editor background. To change how strongly inactive code is faded:

  1. Open Settings | Languages & Frameworks | Haxe | Conditional Compilation.
  2. Set Dim inactive code toward the background (%):. 0 keeps the full colors, 100 hides the code. The default is 40.

Inside an inactive branch:

  • The IDE reports no errors and no inspection warnings. Code for other targets often does not resolve, and that is expected.
  • Typing works as in active code: completion, closing braces, indentation on Enter and Complete Current Statement. Completion there is best effort.
  • Names that refer to active code, for example a class used as a type, still resolve and can be navigated.
  • Declarations are not indexed. A class or function that exists only in an inactive branch is not found by Go to Class… and is not offered by completion in other files until its branch becomes active.

When a condition cannot be evaluated, for example because it contains an error, its branch is treated as inactive.

Colors of directives and conditions

The colors are in Settings | Editor | Color Scheme | Haxe, in the Conditional compilation group:

Entry Used for
Conditional code The directives #if, #elseif, #else and #end, and the text of their conditions.
Defined flag A name in a condition that is currently defined. Bold by default.
Undefined Flag A name in a condition that is not defined.
Error directive The whole #error line.

Conditional code does not change the contents of inactive branches; those keep their own colors, dimmed by the setting above.

Folding

Each branch of a multi-line #if block can be folded; the folded text shows the directive and its condition. To fold inactive branches automatically when a file opens, open Settings | Editor | General | Code Folding and select Haxe: Unused conditionally compiled code: #if/#else/#elseif/#end.

Seeing and switching the define set

  • See which build a file follows: the Context: widget in the status bar names the build file the current file is analyzed under. Click it to pick another build file for that file. See Resolve Context.
  • See the defines: in the Haxe tool window, each build file's Defines group lists the defines the file sets, and its IDE Overrides row lists your overrides. See The Haxe Tool Window.
  • Switch the build configuration: pick another target or section under the build file. For files several build files compile equally, right-click another build file and choose Set as Active Build File. See Build Files and Targets and Sections.
  • Toggle one define from the editor: put the caret on a name in a condition and use Show Context Actions. For a defined name the IDE offers Undefine flag 'name' for build file, for an undefined one Define flag 'name' for build file, for example Define flag 'debug_overlay' for build.hxml. The action edits the IDE Overrides of the build file the file is analyzed under, so your build is not affected. It is only offered when the file has a build file, and not for haxe_ver, haxe, haxe3, haxe4 and haxe5.
  • Complete a define name: inside a condition, basic completion offers the currently defined names with their values. See Code Completion.

When a build file's defines change, for example when you save it or pick another target, the IDE re-reads the files analyzed under it that contain conditional compilation, in the background, and open editors update their highlighting. Picking another build file for one file in the status bar re-reads just that file.

Checks on conditions

The IDE reports, as an error on the condition, the problems that make the compiler stop. One error is shown per condition. Typical problems:

Problem Example Message
An invalid version string #if (version("banana") > version("1.0.0")) Invalid version string "banana". Should follow SemVer.
A version compared with a shortened number #if (hl_ver >= version("1.12.0")) while hl_ver is 1.13 Invalid version "1.13": a version comparison needs all three major.minor.patch parts - did you mean "1.13.0"?
A function other than version() #if (foo("x")) Invalid condition expression
An operator other than !, &&, ||, ==, !=, >, >=, < and <= inside parentheses #if (my-flag), #if (b - a) Unsupported operation: '-' is not allowed in a condition
A leading -, + or ~ #if (-x) Invalid condition expression

Without parentheses, a condition is a single name: #if my-flag tests my. That is why a define written as -D my-flag must be tested as my_flag. The build file editor warns about such a dashed name and offers Replace with 'my_flag'; in a Lime project file the fix also renames the define where if, unless and ${...} use it. See Diagnostics and Inspections.

Conditions the compiler never evaluates are not checked: a condition inside an inactive branch, or an #elseif after a branch that was taken. Comparing an undefined name, as in #if (my_level > 3), is not an error: the comparison is false, and only != is true.

The Conditional Compilation (Preprocessing) inspection checks the structure of #if blocks: a missing #if or #end, an #elseif after #else, a second #else and a duplicate #end. See Diagnostics and Inspections.

#error

#error "message" stops the compilation with that message when the compiler reaches it in an active branch. In the editor, the IDE colors the whole line with Error directive; it does not report it as a problem.

Formatting inactive branches

By default, Reformat Code formats inactive branches with the same rules as active code, as long as they parse. The options are on the Conditional Compilation tab of Settings | Editor | Code Style | Haxe. See Formatting and Code Style.

Clone this wiki locally