Breaking changes in Haxe 4.0.0

Andy Li edited this page Jul 10, 2018 · 15 revisions

Important and breaking changes in Haxe 4

Assigning field in a closure in a setter bugfix

function set_some(value) {
  var old = some;
  some = value;
  rollbacks.push(function() some = old); // <-- bypasses setter now as it should

Use -D haxe3compat to get warnings pointing to places that need to be fixed.

final keyword

final is a new keyword and can no longer be used as an identifier.

abstract Null<T>

The Null<T> type is now @:coreType abstract instead of a typedef to T. It still supports implicit conversion from and to T, as well as forwarding any field access to T, so its basic usage shouldn't be affected at all.

However, this change affects macros. Because it's not a typedef now, it won't be automatically followed by Context.follow and it won't be matched by the TType constructor. Instead it's now represented as a TAbstract instance, so if a macro is handling Null specifically, it needs to be changed to match TAbstract instead of TType. Also if the macro is handling TAbstract, e.g. by recursing into its underlying type, an additional match for the Null case might be needed, unless it's already handling abstracts with the @:coreType metadata.

The reason for this change is mainly to cleanup the inconsistency and confusion about Null within compiler and macro source. That is, on most static platforms, the nullable type is often different than a non-nullable type, for e.g. basic types (Int,Bool,etc), so having Null<T> represented as a mere type alias (which is what typedef is) leads to bugs and general confusion. Moreover, having an abstract Null<T> type sets the ground for possible future exploration regarding null-safety features, because with abstract types we can control implicit casting and variance easier.

Related PR:

New typed AST node: TEnumIndex

A new constructor were added to the haxe.macro.Type.TypedExprDef enum: TEnumIndex(e1:TypedExpr). It represents access to a enum index value and is generated by the pattern matcher.

Before this node was introduced, the pattern matcher was generating a (potentially inlined) Type.enumIndex call that was hard to distinguish later in the optimization filters and generators.

Related PR:

New Typed AST node: TIdent (TODO: TUnbound?)

Identifiers that are not bound to a known field or variable are now represented by a special haxe.macro.Type.TypedExprDef constructor: TIdent(s:String). These identifiers are most often introduced by the use of untyped (e.g. untyped __js__).

Before this node, such identifiers were represented by a TLocal node pointing a fake local variable with @:unbound metadata. This solution wasn't perfect, because compiler/macro developer would expect TLocal to point an actually declared variable, so having to check whether a given local variable is unbound every time is error-prone.

Related PR:

New macro interpreter

The interpreter used for running scripts and executing macros has been rewritten for better performance and maintainability. There are two things to consider regarding backward compatibility in this change:

First, the target name for the interpreter is now eval, not neko, so if the code contains #if neko conditional compilation implying that it also affects interpreter, that has to be changed to #if eval or #if (neko || eval). The macro define is of course still present when running macros.

Second, because of the new target now has a JIT-compilation step, it's a bit more strict about compiling code that was meant for another target. For example, if a module with your macro function also contains a method with code like untyped __js__, the previous interpreter would not complain about it unless you actually execute that method. The new interpreter will fail at JIT-compilation stage. To fix this, the code that is not meant to run on eval should be skipped, for example by fencing it with #if <target-define> or #if !macro, or by moving it into a separate module that won't be loaded in the interpreter context.

Related PR:

Related blog post:

Unicode-aware lexer

Haxe now expects the source files (.hx) to be UTF-8 encoded and positions reported and expected by the compiler now contain character offsets instead of byte offsets.

This is an important change for IDE support maintainers: all display query offsets and all reported positions must be treated as character-based rather than byte-based.

This makes haxe report proper positions for source files containing multi-byte characters, such as Cyrillic, CJK and others. This also should make it easier for editors to integrate with haxe ide services, since they mostly work with character offsets as well. If the editor has the handling position conversion by e.g. encoding/decoding the file on-request, this should be disabled for Haxe 4.

Related issue:

Related PR:

If your IDE have Haxe 3.x support, you can add -D old-error-format to keep compiler completion working using previous behavior. Other advanced completion services will not work with bytes position.

1-based column numbers in compiler errors and warnings

Haxe now reports both line and column number starting from 1. This is more consistent and is what a lot of editors expect when processing build output for capturing error messages.

If you're parsing the position string (e.g. main.hx:3: characters 17-25) for further processing, please note that characters numbers are now 1-based. This can be disabled with -D old-error-format compiler argument.

Related PR:

New syntax: arrow functions

Haxe 4 now supports short arrow function syntax in the form of (a, b) -> a + b which is equivalent to function(a, b) return a + b.

While it's an obvious feature for language users, IDE maintainers will need to add support for this new syntax. In short:

// no args
() -> expr

// with args
(arg1, arg2) -> expr

// args with type hints
(arg1:Type1, arg2:Type2) -> expr

// return type hint is not supported for arrow functions due to syntax ambiguity,
// one can use type-check syntax in the function expression
(arg1:Type) -> (expr:ReturnType)

// single-arg-no-type-hint convenience special case (no parenthesis required)
arg -> expr

Related proposal:

New function type syntax

In addition to the old Int->Void function type syntax, Haxe 4 now has new syntax for specifying function types with support for argument names.

// no args
() -> Void

// unnamed args
(Int, String) -> Bool

// named args
(name:String) -> Void

// named, unnamed and optional args mixed
(arg1:Type1, ?arg2:Type2, Type3) -> Ret

Related proposal:

haxe.unit removal

The classes in haxe.unit have been removed. They are still available from the hx3compat library. For more elaborate unit testing frameworks, consider using utest or munit.

haxe.remoting removal

The classes in haxe.remoting have been removed. They are still available from the hx3compat library.

List to Array

Some occurrences of List in the standard library have been replaced with Array. This might require some small changes in user code.

haxe.xml.Fast is now an abstract

While usage remains the same, it is no longer possible to use haxe.xml.Fast as a class (e.g. extending it or using reflection on it).

Syntax change for multiple parameter constraints

The syntax T:(A, B) is no longer supported and has been replaced by T : A & B. We decided to break this because there was a syntactic conflict.


Static extension restrictions

  1. Static extensions no longer resolve on an implicit this type. This was a little-known feature which could affect resolution order in some instances (related:
  2. Static extensions no longer apply abstract field-casts. (
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.