Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
587 lines (389 sloc) 35.9 KB
 \chapter{Type System} \label{type-system} We learned about the different kinds of types in \Fullref{types} and it is now time to see how they interact with each other. We start off easy by introducing \tref{typedef}{type-system-typedef}, a mechanism to give a name (or alias) to a more complex type. Among other things, this will come in handy when working with types having \tref{type parameters}{type-system-type-parameters}. A lot of type-safety is achieved by checking if two given types of the type groups above are compatible. Meaning, the compiler tries to perform \emph{unification} between them as detailed in \Fullref{type-system-unification}. All types are organized in \emph{modules} and can be addressed through \emph{paths}. \Fullref{type-system-modules-and-paths} will give a detailed explanation of the related mechanics. \section{Typedef} \label{type-system-typedef} We briefly looked at typedefs while talking about \tref{anonymous structures}{types-anonymous-structure} and saw how we could shorten a complex \tref{structure type}{types-anonymous-structure} by giving it a name. This is precisely what typedefs are good for. Giving names to structure types might even be considered their primary use. In fact, it is so common that the distinction appears somewhat blurry and many Haxe users consider typedefs to actually \emph{be} the structure. A typedef can give a name to any other type: \begin{lstlisting} typedef IA = Array; \end{lstlisting} This enables us to use \expr{IA} in places where we would normally use \expr{Array$<$Int$>$}. While this saves only a few keystrokes in this particular case, it can make a much bigger difference for more complex, compound types. Again, this is why typedef and structures seem so connected: \begin{lstlisting} typedef User = { var age : Int; var name : String; } \end{lstlisting} A typedef is not a textual replacement but actually a real type. It can even have \tref{type parameters}{type-system-type-parameters} as the \type{Iterable} type from the Haxe Standard Library demonstrates: \begin{lstlisting} typedef Iterable = { function iterator() : Iterator; } \end{lstlisting} \section{Type Parameters} \label{type-system-type-parameters} Haxe allows parametrization of a number of types, as well as \tref{class fields}{class-field} and \tref{enum constructors}{types-enum-constructor}. Type parameters are defined by enclosing comma-separated type parameter names in angle brackets \expr{$<>$}. A simple example from the Haxe Standard Library is \type{Array}: \begin{lstlisting} class Array { function push(x : T) : Int; } \end{lstlisting} Whenever an instance of \type{Array} is created, its type parameter \type{T} becomes a \tref{monomorph}{types-monomorph}. That is, it can be bound to any type, but only one at a time. This binding can happen \begin{description} \item[explicitly] by invoking the constructor with explicit types (\expr{new Array$<$String$>$()}) or \item[implicitly] by \tref{type inference}{type-system-type-inference}, e.g. when invoking \expr{arrayInstance.push("foo")}. \end{description} Inside the definition of a class with type parameters, these type parameters are an unspecific type. Unless \tref{constraints}{type-system-type-parameter-constraints} are added, the compiler has to assume that the type parameters could be used with any type. As a consequence, it is not possible to access fields of type parameters or \tref{cast}{expression-cast} to a type parameter type. It is also not possible to create a new instance of a type parameter type, unless the type parameter is \tref{generic}{type-system-generic} and constrained accordingly. The following table shows where type parameters are allowed: \begin{center} \begin{tabular}{| l | l | l |} \hline Parameter on & Bound upon & Notes \\ \hline Class & instantiation & Can also be bound upon member field access. \\ Enum & instantiation & \\ Enum Constructor & instantiation & \\ Function & invocation & Allowed for methods and named local lvalue functions. \\ Structure & instantiation & \\ \hline \end{tabular} \end{center} With function type parameters being bound upon invocation, such a type parameter (if unconstrained) accepts any type. However, only one type per invocation is accepted. This can be utilized if a function has multiple arguments: \haxe{assets/FunctionTypeParameter.hx} Both arguments \expr{expected} and \expr{actual} of the \expr{equals} function have type \type{T}. This implies that for each invocation of \expr{equals} the two arguments must be of the same type. The compiler admits the first call (both arguments being of \type{Int}) and the second call (both arguments being of \type{String}) but the third attempt causes a compiler error. \trivia{Type parameters in expression syntax}{We often get the question why a method with type parameters cannot be called as \expr{method(x)}. The error messages the compiler gives are not very helpful. However, there is a simple reason for that: The above code is parsed as if both \expr{<} and \expr{>} were binary operators, yielding \expr{(method < String) > (x)}.} \subsection{Constraints} \label{type-system-type-parameter-constraints} Type parameters can be constrained to multiple types: \haxe{assets/Constraints.hx} Type parameter \type{T} of method \expr{test} is constrained to the types \type{Iterable$<$String$>$} and \type{Measurable}. The latter is defined using a \tref{typedef}{type-system-typedef} for convenience and requires compatible types to have a read-only \tref{property}{class-field-property} named \expr{length} of type \type{Int}. The constraints then say that a type is compatible if \begin{itemize} \item it is compatible with \type{Iterable$<$String$>$} and \item has a \expr{length}-property of type \type{Int}. \end{itemize} We can see that invoking \expr{test} with an empty array in line 7 and an \type{Array$<$String$>$} in line 8 works fine. This is because \type{Array} has both a \expr{length}-property and an \expr{iterator}-method. However, passing a \type{String} as argument in line 9 fails the constraint check because \type{String} is not compatible with \type{Iterable$<$T$>$}. When constraining to a single type, the parentheses can be omitted: \haxe{assets/Constraints2.hx} \since{4.0.0} One of the breaking changes between versions 3 and 4 is the multiple type constraint syntax. As the first example above shows, in Haxe 4 the constraints are separated by a \expr{\&} symbol instead of a comma. This is similar to the new \tref{structure extension}{types-structure-extensions} syntax. \section{Generic} \label{type-system-generic} Usually, the Haxe Compiler generates only a single class or function even if it has type parameters. This results in a natural abstraction where the code generator for the target language has to assume that a type parameter could be of any type. The generated code then might have to perform some type checks which can be detrimental to performance. A class or function can be made \emph{generic} by attributing it with the \expr{:generic} \tref{metadata}{lf-metadata}. This causes the compiler to emit a distinct class/function per type parameter combination with mangled names. A specification like this can yield a boost in performance-critical code portions on \tref{static targets}{define-static-target} at the cost of a larger output size: \haxe{assets/GenericClass.hx} It seems unusual to see the explicit type \type{MyValue} here as we usually let \tref{type inference}{type-system-type-inference} deal with this. Nonetheless, it is indeed required in this case. The compiler has to know the exact type of a generic class upon construction. The \target{JavaScript} output shows the result: \lang{js}\begin{lstlisting} (function () { "use strict"; var Test = function() { }; Test.main = function() { var a = new MyValue_String("Hello"); var b = new MyValue_Int(5); }; var MyValue_Int = function(value) { this.value = value; }; var MyValue_String = function(value) { this.value = value; }; Test.main(); })(); \end{lstlisting} We can identify that \type{MyValue} and \type{MyValue} have become \type{MyValue_String} and \type{MyValue_Int} respectively. This is similar for generic functions: \haxe{assets/GenericFunction.hx} Again, the \target{JavaScript} output makes it obvious: \lang{js}\begin{lstlisting} (function () { "use strict"; var Main = function() { } Main.method_Int = function(t) { } Main.method_String = function(t) { } Main.main = function() { Main.method_String("foo"); Main.method_Int(1); } Main.main(); })(); \end{lstlisting} \subsection{Construction of generic type parameters} \label{type-system-generic-type-parameter-construction} \define{Generic Type Parameter}{define-generic-type-parameter}{A type parameter is said to be generic if its containing class or method is generic.} It is not possible to construct normal type parameters, e.g. \expr{new T()} is a compiler error. The reason for this is that Haxe generates only a single function and the construct makes no sense in that case. This is different when the type parameter is generic: Since we know that the compiler will generate a distinct function for each type parameter combination, it is possible to replace the \type{T} \expr{new T()} with the real type. \haxe{assets/GenericTypeParameter.hx} It should be noted that \tref{top-down inference}{type-system-top-down-inference} is used here to determine the actual type of \type{T}. There are two requirements for this kind of type parameter construction to work: The constructed type parameter must be \begin{enumerate} \item generic and \item be explicitly \tref{constrained}{type-system-type-parameter-constraints} to having a \tref{constructor}{types-class-constructor}. \end{enumerate} Here, 1. is given by \expr{make} having the \expr{@:generic} metadata, and 2. by \type{T} being constrained to \type{Constructible}. The constraint holds for both \type{String} and \type{haxe.Template} as both have a constructor accepting a singular \type{String} argument. Sure enough, the relevant \target{JavaScript} output looks as expected: \lang{js}\begin{lstlisting} var Main = function() { } Main.__name__ = true; Main.make_haxe_Template = function() { return new haxe.Template("foo"); } Main.make_String = function() { return new String("foo"); } Main.main = function() { var s = Main.make_String(); var t = Main.make_haxe_Template(); } \end{lstlisting} \section{Variance} \label{type-system-variance} While variance is also relevant in other places, it occurs particularly often with type parameters and comes as a surprise in this context. Additionally, it is very easy to trigger variance errors: \haxe{assets/Variance.hx} Apparently, an \type{Array} cannot be assigned to an \type{Array}, even though \type{Child} can be assigned to \type{Base}. The reason for this might be somewhat unexpected: It is not allowed because arrays can be written to, e.g. via their \expr{push()} method. It is easy to generate problems by ignoring variance errors: \haxe{assets/Variance2.hx} Here we subvert the type checker by using a \tref{cast}{expression-cast}, thus allowing the assignment after the commented line. With that we hold a reference \expr{bases} to the original array, typed as \type{Array}. This allows pushing another type compatible with \type{Base} (\type{OtherChild}) onto that array. However, our original reference \expr{children} is still of type \type{Array} and things go bad when we encounter the \type{OtherChild} instance in one of its elements while iterating. If \type{Array} had no \expr{push()} method and no other means of modification, the assignment would be safe because no incompatible type could be added to it. In Haxe, we can achieve this by restricting the type accordingly using \tref{structural subtyping}{type-system-structural-subtyping}: \haxe{assets/Variance3.hx} We can safely assign with \expr{b} being typed as \type{MyArray} and \type{MyArray} only having a \expr{pop()} method. There is no method defined on \type{MyArray} which could be used to add incompatible types, it is thus said to be \emph{covariant}. \define{Covariance}{define-covariance}{A \tref{compound type}{define-compound-type} is considered covariant if its component types can be assigned to less specific components, i.e. if they are only read, but never written.} \define{Contravariance}{define-contravariance}{A \tref{compound type}{define-compound-type} is considered contravariant if its component types can be assigned to less generic components, i.e. if they are only written, but never read.} \section{Unification} \label{type-system-unification} \todo{Mention toString()/String conversion somewhere in this chapter.} Unification is the heart of the type system and contributes immensely to the robustness of Haxe programs. It describes the process of checking if a type is compatible to another type. \define{Unification}{define-unification}{Unification between two types A and B is a directional process which answers the question if A \emph{can be assigned to} B. It may \emph{mutate} either type if it is or has a \tref{monomorph}{types-monomorph}.} Unification errors are very easy to trigger: \begin{lstlisting} class Main { static public function main() { // Int should be String var s:String = 1; } } \end{lstlisting} We try to assign a value of type \type{Int} to a variable of type \type{String}, which causes the compiler to try and \emph{unify Int with String}. This is, of course, not allowed and makes the compiler emit the error \expr{Int should be String}. In this particular case, the unification is triggered by an \emph{assignment}, a context in which the is assignable to'' definition is intuitive. It is one of several cases where unification is performed: \begin{description} \item[Assignment:] If \expr{a} is assigned to \expr{b}, the type of \expr{a} is unified with the type of \expr{b}. \item[Function call:] We have briefly seen this one while introducing the \tref{function}{types-function} type. In general, the compiler tries to unify the first given argument type with the first expected argument type, the second given argument type with the second expected argument type and so on until all argument types are handled. \item[Function return:] Whenever a function has a \expr{return e} expression, the type of \expr{e} is unified with the function return type. If the function has no explicit return type, it is inferred to the type of \expr{e} and subsequent \expr{return} expressions are inferred against it. \item[Array declaration:] The compiler tries to find a minimal type between all given types in an array declaration. Refer to \Fullref{type-system-unification-common-base-type} for details. \item[Object declaration:] If an object is declared against'' a given type, the compiler unifies each given field type with each expected field type. \item[Operator unification:] Certain operators expect certain types which the given types are unified against. For instance, the expression \expr{a \&\& b} unifies both \expr{a} and \expr{b} with \type{Bool} and the expression \expr{a == b} unifies \expr{a} with \expr{b}. \end{description} \subsection{Between Class/Interface} \label{type-system-unification-between-classes-and-interfaces} When defining unification behavior between classes, it is important to remember that unification is directional: We can assign a more specialized class (e.g. a child class) to a generic class (e.g. a parent class) but the reverse is not valid. The following assignments are allowed: \begin{itemize} \item child class to parent class \item class to implementing interface \item interface to base interface \end{itemize} These rules are transitive, meaning that a child class can also be assigned to the base class of its base class, an interface its base class implements, the base interface of an implementing interface and so on. \todo{''parent class'' should probably be used here, but I have no idea what it means, so I will refrain from changing it myself.} \subsection{Structural Subtyping} \label{type-system-structural-subtyping} \define{Structural Subtyping}{define-structural-subtyping}{Structural subtyping defines an implicit relation between types that have the same structure.} Structural sub-typing in Haxe is allowed when unifying \begin{itemize} \item a \tref{class}{types-class-instance} with a \tref{structure}{types-anonymous-structure} and \item a structure with another structure. \end{itemize} The following example is part of the \type{Lambda} class of the \tref{Haxe Standard Library}{std}: \begin{lstlisting} public static function empty(it : Iterable):Bool { return !it.iterator().hasNext(); } \end{lstlisting} The \expr{empty}-method checks if an \type{Iterable} has an element. For this purpose, it is not necessary to know anything about the argument type other than the fact that it is considered an iterable. This allows calling the \expr{empty}-method with any type that unifies with \type{Iterable$<$T$>$} which applies to a lot of types in the Haxe Standard Library. This kind of typing can be very convenient but extensive use may be detrimental to performance on static targets, which is detailed in \Fullref{types-structure-performance}. \subsection{Monomorphs} \label{type-system-monomorphs} Unification of types having or being a \tref{monomorph}{types-monomorph} is detailed in \Fullref{type-system-type-inference}. \subsection{Function Return} \label{type-system-unification-function-return} Unification of function return types may involve the \tref{\type{Void}-type}{types-void} and requires a clear definition of what unifies with \type{Void}. With \type{Void} describing the absence of a type, it is not assignable to any other type, not even \type{Dynamic}. This means that if a function is explicitly declared as returning \type{Dynamic}, it cannot return \type{Void}. The opposite applies as well: If a function declares a return type of \type{Void}, it cannot return \type{Dynamic} or any other type. However, this direction of unification is allowed when assigning function types: \begin{lstlisting} var func:Void->Void = function() return "foo"; \end{lstlisting} The right-hand function clearly is of type \type{Void->String}, yet we can assign it to the variable \expr{func} of type \type{Void->Void}. This is because the compiler can safely assume that the return type is irrelevant, given that it could not be assigned to any non-\type{Void} type. \subsection{Common Base Type} \label{type-system-unification-common-base-type} Given a set of multiple types, a \emph{common base type} is a type which all types of the set unify against: \haxe{assets/UnifyMin.hx} Although \type{Base} is not mentioned, the Haxe Compiler manages to infer it as the common type of \type{Child1} and \type{Child2}. The Haxe Compiler employs this kind of unification in the following situations: \begin{itemize} \item array declarations \item \expr{if}/\expr{else} \item cases of a \expr{switch} \end{itemize} \section{Type Inference} \label{type-system-type-inference} The effects of type inference have been seen throughout this document and will continue to be important. A simple example shows type inference at work: \haxe{assets/TypeInference.hx} The special construct \expr{\$type} was previously mentioned in order to simplify the explanation of the \Fullref{types-function} type, so let us now introduce it officially: %TODO:$type \define[Construct]{\expr{\$type}}{define-dollar-type}{\expr{\$type} is a compile-time mechanism being called like a function, with a single argument. The compiler evaluates the argument expression and then outputs the type of that expression.} In the example above, the first \expr{\\$type} prints \expr{Unknown<0>}. This is a \tref{monomorph}{types-monomorph}, a type that is not yet known. The next line \expr{x = "foo"} assigns a \type{String} literal to \expr{x}, which causes the \tref{unification}{type-system-unification} of the monomorph with \type{String}. We then see that the type of \expr{x} indeed has changed to \type{String}. Whenever a type other than \Fullref{types-dynamic} is unified with a monomorph, that monomorph \emph{becomes} that type: it \emph{morphs} into that type. Therefore it cannot morph into a different type afterwards, a property expressed in the \emph{mono} part of its name. Following the rules of unification, type inference can occur in compound types: \haxe{assets/TypeInference2.hx} Variable \expr{x} is first initialized to an empty \type{Array}. At this point we can tell that the type of \expr{x} is an array, but we do not yet know the type of the array elements. Consequentially, the type of \expr{x} is \type{Array>}. It is only after pushing a \type{String} onto the array that we know the type to be \type{Array}. \subsection{Top-down Inference} \label{type-system-top-down-inference} Most of the time, types are inferred on their own and may then be unified with an expected type. In a few places, however, an expected type may be used to influence inference. We then speak of \emph{top-down inference}. \define{Expected Type}{define-expected-type}{Expected types occur when the type of an expression is known before that expression has been typed, e.g. because the expression is argument to a function call. They can influence typing of that expression through what is called \tref{top-down inference}{type-system-top-down-inference}.} A good example are arrays of mixed types. As mentioned in \Fullref{types-dynamic}, the compiler refuses \expr{[1, "foo"]} because it cannot determine an element type. Employing top-down inference, this can be overcome: \haxe{assets/TopDownInference.hx} Here, the compiler knows while typing \expr{[1, "foo"]} that the expected type is \type{Array}, so the element type is \type{Dynamic}. Instead of the usual unification behavior where the compiler would attempt (and fail) to determine a \tref{common base type}{type-system-unification-common-base-type}, the individual elements are typed against and unified with \type{Dynamic}. We have seen another interesting use of top-down inference when \tref{construction of generic type parameters}{type-system-generic-type-parameter-construction} was introduced: \haxe{assets/GenericTypeParameter.hx} The explicit types \type{String} and \type{haxe.Template} are used here to determine the return type of \expr{make}. This works because the method is invoked as \expr{make()}, so we know the return type will be assigned to the variables. Utilizing this information, it is possible to bind the unknown type \type{T} to \type{String} and \type{haxe.Template} respectively. % this is not really top down inference %Top-down inference is also utilized when dealing with \tref{enum constructors}{types-enum-constructor}: %\haxe{assets/TopDownInference2.hx} %The constructors \expr{TObject} and \expr{TFunction} of type \expr{ValueType} are recognized even though their containing module \type{Type} is not \tref{imported}{Import}. This is possible because the return type of \expr{Type.typeof("foo")} is known to be \expr{ValueType}. \subsection{Limitations} \label{type-system-inference-limitations} Type inference saves a lot of manual type hints when working with local variables, but sometimes the type system still needs some help. In fact, it does not even try to infer the type of a \tref{variable}{class-field-variable} or \tref{property}{class-field-property} field unless it has a direct initialization. There are also some cases involving recursion where type inference has limitations. If a function calls itself recursively while its type is not (completely) known yet, type inference may infer a wrong, too specialized type. A different kind of limitation involves the readability of code. If type inference is overused it might be difficult to understand parts of a program due to the lack of visible types. This is particularly true for method signatures. It is recommended to find a good balance between type inference and explicit type hints. \section{Modules and Paths} \label{type-system-modules-and-paths} \define{Module}{define-module}{All Haxe code is organized in modules, which are addressed using paths. In essence, each .hx file represents a module which may contain several types. A type may be \expr{private}, in which case only its containing module can access it.} The distinction of a module and its containing type of the same name is blurry by design. In fact, addressing \expr{haxe.ds.StringMap} can be considered shorthand for \expr{haxe.ds.StringMap.StringMap}. The latter version consists of four parts: \begin{enumerate} \item the package \expr{haxe.ds} \item the module name \expr{StringMap} \item the type name \type{StringMap} \item the type parameter \type{Int} \end{enumerate} If the module and type name are equal, the duplicate can be removed, leading to the \expr{haxe.ds.StringMap} short version. However, knowing about the extended version helps with understanding how \tref{module sub-types}{type-system-module-sub-types} are addressed. Paths can be shortened further by using an \tref{import}{type-system-import}, which typically allows omitting the package part of a path. This may lead to usage of unqualified identifiers, for which understanding the \tref{resolution order}{type-system-resolution-order} is required. \define{Type path}{define-type-path}{The (dot-)path to a type consists of the package, the module name and the type name. Its general form is \expr{pack1.pack2.packN.ModuleName.TypeName}.} \subsection{Module Sub-Types} \label{type-system-module-sub-types} A module sub-type is a type declared in a module with a different name than that module. This allows a single .hx file to contain multiple types, which can be accessed unqualified from within the module, and by using \expr{package.Module.Type} from other modules: \begin{lstlisting} var e:haxe.macro.Expr.ExprDef; \end{lstlisting} Here the sub-type \type{ExprDef} within module \expr{haxe.macro.Expr} is accessed. An example sub-type declaration would look like the following : \begin{lstlisting} // a/A.hx package a; class A { public function new() {} } // sub-type class B { public function new() {} } \end{lstlisting} \begin{lstlisting} // Main.hx import a.A; class Main { static function main() { var subtype1 = new a.A.B(); // these are also valid, but require import a.A or import a.A.B : var subtype2 = new B(); var subtype3 = new a.B(); } } \end{lstlisting} The sub-type relation is not reflected at run-time. That is, public sub-types become a member of their containing package, which could lead to conflicts if two modules within the same package tried to define the same sub-type. Naturally, the Haxe compiler detects these cases and reports them accordingly. In the example above \type{ExprDef} is generated as \type{haxe.macro.ExprDef}. Sub-types can also be made private: \begin{lstlisting} private class C { ... } private enum E { ... } private typedef T { ... } private abstract A { ... } \end{lstlisting} \define{Private type}{define-private-type}{A type can be made private by using the \expr{private} modifier. As a result, the type can only be directly accessed from within the \tref{module}{define-module} it is defined in. Private types, unlike public ones, do not become a member of their containing package.} The accessibility of types can be controlled more fine-grained by using \tref{access control}{lf-access-control}. \subsection{Import} \label{type-system-import} If a type path is used multiple times in a .hx file, it might make sense to use an \expr{import} to shorten it. This allows omitting the package when using the type: \haxe{assets/Import.hx} With \expr{haxe.ds.StringMap} being imported in the first line, the compiler is able to resolve the unqualified identifier \expr{StringMap} in the \expr{main} function to this package. The module \type{StringMap} is said to be \emph{imported} into the current file. In this example, we are actually importing a \emph{module}, not just a specific type within that module. This means that all types defined within the imported module are available: \haxe{assets/Import2.hx} The type \type{Binop} is an \tref{enum}{types-enum-instance} declared in the module \type{haxe.macro.Expr}, and thus available after the import of said module. If we were to import only a specific type of that module, e.g. \expr{import haxe.macro.Expr.ExprDef}, the program would fail to compile with \expr{Class not found : Binop}. There are several aspects worth knowing about importing: \begin{itemize} \item The bottommost import takes priority (detailed in \Fullref{type-system-resolution-order}). \item The \tref{static extension}{lf-static-extension} keyword \expr{using} implies the effect of \expr{import}. \item If an enum is imported (directly or as part of a module import), all its \tref{enum constructors}{types-enum-constructor} are also imported (this is what allows the \expr{OpAdd} usage in the above example). \end{itemize} Furthermore, it is also possible to import \tref{static fields}{class-field} of a class and use them unqualified: \haxe{assets/Import3.hx} Special care has to be taken with field names or local variable names that conflict with a package name: Since they take priority over packages, a local variable named \expr{haxe} blocks off usage of the entire \expr{haxe} package. \paragraph{Wildcard import} Haxe allows using \expr{.*} to allow import of all modules in a package, all types in a module or all static fields in a type. It is important to understand that this kind of import only crosses a single level as we can see in the following example: \haxe{assets/ImportWildcard.hx} Using the wildcard import on \expr{haxe.macro} allows accessing \type{Expr} which is a module in this package, but it does not allow accessing \type{ExprDef} which is a sub-type of the \type{Expr} module. This rule extends to static fields when a module is imported. When using wildcard imports on a package the compiler does not eagerly process all modules in that package. This means that these modules are never actually seen by the compiler unless used explicitly and are then not part of the generated output. \paragraph{Import with alias} If a type or static field is used a lot in an importing module it might help to alias it to a shorter name. This can also be used to disambiguate conflicting names by giving them a unique identifier. \haxe{assets/ImportAlias.hx} Here we import \expr{String.fromCharCode} as \expr{f} which allows us to use \expr{f(65)} and \expr{f(66)}. While the same could be achieved with a local variable, this method is compile-time exclusive and guaranteed to have no run-time overhead. \since{3.2.0} Haxe also allows the more natural \expr{as} in place of \expr{in}. \subsection{Import defaults / import.hx} \label{type-system-import-defaults} \since{3.3.0} This feature allows us to define default imports and usings that will be applied for all modules inside a directory, which is particularly handy for large code bases with a lot of helpers and static extensions because it reduces the amount of imports. The special named \ic{import.hx} file (note the lowercase name) can be placed in the directory where your code lies. The file can contain import and using statements only. The statements will be applied to all Haxe modules in this directory and its subdirectories. Default imports of \ic{import.hx} acts as if its contents is placed at the top of each module. \paragraph{Related content} \begin{itemize} \item \href{https://haxe.org/blog/importhx-intro/}{Introduction of \ic{import.hx}} \end{itemize} \subsection{Resolution Order} \label{type-system-resolution-order} Resolution order comes into play as soon as unqualified identifiers are involved. These are \tref{expressions}{expression} in the form of \expr{foo()}, \expr{foo = 1} and \expr{foo.field}. The last one in particular includes module paths such as \expr{haxe.ds.StringMap}, where \expr{haxe} is an unqualified identifier. We describe the resolution order algorithm here, which depends on the following state: \begin{itemize} \item the declared \tref{local variables}{expression-var} (including function arguments) \item the \tref{imported}{type-system-import} modules, types and statics \item the available \tref{static extensions}{lf-static-extension} \item the kind (static or member) of the current field \item the declared member fields on the current class and its parent classes \item the declared static fields on the current class \item the \tref{expected type}{define-expected-type} \item the expression being \expr{untyped} or not \end{itemize} \todo{proper label and caption + code/identifier styling for diagram} \input{assets/tikz/resolution-order.tex} Given an identifier \expr{i}, the algorithm is as follows: \begin{enumerate} \item If i is \expr{true}, \expr{false}, \expr{this}, \expr{super} or \expr{null}, resolve to the matching constant and halt. \item If a local variable named \expr{i} is accessible, resolve to it and halt. \item If the current field is static, go to \ref{resolution:static-lookup}. \item If the current class or any of its parent classes has a field named \expr{i}, resolve to it and halt. \item\label{resolution:static-extension} If a static extension with a first argument of the type of the current class is available, resolve to it and halt. \item\label{resolution:static-lookup} If the current class has a static field named \expr{i}, resolve to it and halt. \item\label{resolution:enum-ctor} If an enum constructor named \expr{i} is declared on an imported enum, resolve to it and halt. \item If a static named \expr{i} is explicitly imported, resolve to it and halt. \item If \expr{i} starts with a lower-case character, go to \ref{resolution:untyped}. \item\label{resolution:type} If a type named \expr{i} is available, resolve to it and halt. \item\label{resolution:untyped} If the expression is not in untyped mode, go to \ref{resolution:failure} \item If \expr{i} equals \expr{__this__}, resolve to the \expr{this} constant and halt. \item Generate a local variable named \expr{i}, resolve to it and halt. \item\label{resolution:failure} Fail \end{enumerate} For step \ref{resolution:type}, it is also necessary to define the resolution order of types: \begin{enumerate} \item\label{resolution:import} If a type named \expr{i} is imported (directly or as part of a module), resolve to it and halt. \item If the current package contains a module named \expr{i} with a type named \expr{i}, resolve to it and halt. \item If a type named \expr{i} is available at top-level, resolve to it and halt. \item Fail \end{enumerate} For step \ref{resolution:import} of this algorithm as well as steps \ref{resolution:static-extension} and \ref{resolution:enum-ctor} of the previous one, the order of import resolution is important: \begin{itemize} \item Imported modules and static extensions are checked from bottom to top with the first match being picked. \item Imports that come from \tref{import.hx}{type-system-import-defaults} files are considered to be at the top of affected modules, which means they have lowest priority. If multiple \ic{import.hx} files affect a module, the ones in child directories have priority over the ones in parent directories. \item Within a given module, types are checked from top to bottom. \item For imports, a match is made if the name equals. \item For \tref{static extensions}{lf-static-extension}, a match is made if the name equals and the first argument \tref{unifies}{type-system-unification}. Within a given type being used as static extension, the fields are checked from top to bottom. \end{itemize} \section{untyped} \label{type-system-untyped} \emph{Important note:} This syntax should be avoided whenever possible. The produced code cannot be properly checked by the Haxe compiler and so it may have type errors or other bugs that would be caught at compile time in regular code. Use only when absolutely necessary and when you know what you are doing. It is possible to completely circumvent the type checker by prefixing an expression with the keyword \expr{untyped}. The majority of type errors are not emitted inside an untyped expression. This is primarily used with the target-specific \tref{code injection expressions}{target-syntax}.
You can’t perform that action at this time.