├── 0000-template.md ├── README.md ├── images └── hxx-example.gif └── proposals ├── 0001-any.md ├── 0002-arrow-functions.md ├── 0003-new-function-type.md ├── 0004-intersection-types.md ├── 0005-key-value-iter.md ├── 0006-inline-markup.md ├── 0007-module-level-funcs.md ├── 0008-macro-forward.md ├── 0009-inline-calls.md ├── 0010-asys.md ├── 0011-local-var-metadata.md ├── 0012-abstract-classes.md ├── 0013-default-type-parameters.md ├── 0014-self-access-for-abstracts.md ├── 0015-local-static-variables.md ├── 0016-null-coalescing-operator.md ├── 0017-null-safe-navigation-operator.md ├── 0018-number-separators.md └── 0019-numeric-iteral-suffixes.md /0000-template.md: -------------------------------------------------------------------------------- 1 | # ::enter feature name here:: 2 | 3 | * Proposal: [HXP-NNNN](NNNN-filename.md) 4 | * Author: [Haxe Developer](https://github.com/haxedev) 5 | 6 | ## Introduction 7 | 8 | Short description of the proposed feature. Keep it short, so the reader 9 | can quickly get what's it all about. 10 | 11 | ## Motivation 12 | 13 | Describe the problems addressed by this feature. If a similar effect 14 | can be achieved without it with some workarounds, describe the drawbacks 15 | of the workaround. If it's something completely new, show how it will 16 | help developers write better Haxe code or how it will improve the generation 17 | of target code by the compiler. 18 | 19 | ## Detailed design 20 | 21 | Describe the proposed design in details the way language user can understand 22 | and compiler developer can implement. Show corner cases, provide usage examples, 23 | describe how this solution is better than current workarounds. 24 | 25 | ## Impact on existing code 26 | 27 | What impact this change will have on existing code? Will it break compilation? 28 | Will it compile, but break in run-time? How easy it is to migrate existing Haxe code? 29 | 30 | ## Drawbacks 31 | 32 | Describe the drawbacks of the proposed design worth consideration. This doesn't include 33 | breaking changes, since that's described in the previous section. 34 | 35 | ## Alternatives 36 | 37 | What alternatives have you considered to address the same problem, why the proposed solution is better? 38 | 39 | ## Opening possibilities 40 | 41 | Does this change make other future changes possible or easier? Leave this section out if the proposed change 42 | is completely self-contained. 43 | 44 | ## Unresolved questions 45 | 46 | Which parts of the design in question is still to be determined? 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Haxe change proposals 2 | 3 | This project is for maintaining formal change proposals for Haxe language. 4 | 5 | ## List of accepted proposals 6 | 7 | | # | Name | Author | Status | 8 | | --- | --- | --- | --- | 9 | | `0001` | [Any type](proposals/0001-any.md) | [Dan Korostelev](https://github.com/nadako) | implemented in 3.3.0 | 10 | | `0002` | [Arrow functions](proposals/0002-arrow-functions.md) | [Alexander Kuzmenko](https://github.com/RealyUniqueName) | implemented in 4.0.0 | 11 | | `0003` | [New function type syntax](proposals/0003-new-function-type.md) | [Dan Korostelev](https://github.com/nadako) | implemented in 4.0.0 | 12 | | `0004` | [Intersection types](proposals/0004-intersection-types.md) | [Simn](https://github.com/simn) | implemented in 4.0.0 | 13 | | `0005` | [key => value iteration syntax](proposals/0005-key-value-iter.md) | [Dan Korostelev](https://github.com/nadako) | implemented in 4.0.0 | 14 | | `0006` | [Inline markup](proposals/0006-inline-markup.md) | [Juraj Kirchheim](https://github.com/back2dos) | implemented in 4.0.0 | 15 | | `0007` | [Module-level functions and variables](proposals/0007-module-level-funcs.md) | [Dan Korostelev](https://github.com/nadako) | [implemented in Haxe 4.2.0](https://github.com/HaxeFoundation/haxe/pull/8460) | 16 | | `0008` | [Expression macro method forwarding](proposals/0008-macro-forward.md) | [Dan Korostelev](https://github.com/nadako) | [to be implemented](https://github.com/HaxeFoundation/haxe/issues/7453) | 17 | | `0009` | [Inlining functions at call location](proposals/0009-inline-calls.md) | [YellowAfterlife](https://github.com/yellowafterlife) | implemented in 4.0.0 | 18 | | `0010` | [New `asys` APIs](proposals/0010-asys.md) | [Aurel Bílý](https://github.com/Aurel300) | [to be implemented](https://github.com/Aurel300/haxe-sys) | 19 | | `0011` | [Local variable metadata syntax](proposals/0011-local-var-metadata.md) | [Peter Achberger](https://github.com/antriel) | [implemented in 4.2.0](https://github.com/HaxeFoundation/haxe/issues/9618) | 20 | | `0012` | [Abstract classes](proposals/0012-abstract-classes.md) | [Aleksandr Kuzmenko](https://github.com/RealyUniqueName) | [implemented in 4.2.0](https://github.com/HaxeFoundation/haxe/pull/9716) | 21 | | `0013` | [Default type parameters](proposals/0013-default-type-parameters.md) | [Ben Merckx](https://github.com/benmerckx) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10518) | 22 | | `0014` | [Self access for abstracts](proposals/0014-self-access-for-abstracts.md) | [Mark Knol](https://github.com/markknol) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10513) | 23 | | `0015` | [Local static variables](proposals/0015-local-static-variables.md) | [YellowAfterlife](https://github.com/yellowafterlife) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10555) | 24 | | `0016` | [Null coalescing operator](proposals/0016-null-coalescing-operator.md) | [RblSb](https://github.com/RblSb) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10428) | 25 | | `0017` | [Null-safe navigation operator](proposals/0017-null-safe-navigation-operator.md) | [Robert Borghese](https://github.com/RobertBorghese) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10561) | 26 | | `0018` | [Number separators](proposals/0018-number-separators.md) | [Marcelo Silva Nascimento Mancini](https://github.com/MrcSnm) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10514) | 27 | | `0019` | [Numeric literal suffixes](proposals/0019-numeric-iteral-suffixes.md) | [Aidan Lee](https://github.com/aidan63) | [implemented in 4.3.0](https://github.com/HaxeFoundation/haxe/pull/10493) | 28 | 29 | ## About proposals 30 | 31 | Haxe Proposals (HXP) can be submitted by any Haxe team developer or community member, as long as it's complete and well explained (please see the "How to submit an HXP" section below). 32 | 33 | Once a HXP has been submitted, it can be discussed and modified. Anybody can also submit a PR against an existing HXP to have it amended. 34 | 35 | > Please understand that the HXP discussion and voting process is for 36 | > cases where the core team is not opposed to the proposed 37 | > changes. The team internally discusses all proposals; if they are 38 | > opposed to a given HXP then it may be rejected with very little 39 | > public discussion in the PR comments. There is simply not enough 40 | > time for the core team to provide detailed written rationale for 41 | > every proposed change which they think would not be a good overall 42 | > fit for Haxe. 43 | 44 | After the HXP has been discussed, a formal vote can take place to accept it. Only Haxe Core Team members are allowed to vote: 45 | 46 | - Nicolas Cannasse [@ncannasse](https://github.com/ncannasse) (Haxe creator) 47 | - Simon Krajewski [@Simn](https://github.com/Simn) (Compiler Maintainer) 48 | - Hugh Sanderson [@hughsando](https://github.com/hughsando) (Haxe C++ backend) 49 | - Aurel Bílý [@aurel300](https://github.com/Aurel300) (Haxe contributor) 50 | - Andy Li [@andyli](https://github.com/andyli) (tools and infrastructure) 51 | - Dan Korostelev [@nadako](https://github.com/nadako) (compiler contributor) 52 | - Alexander Kuzmenko [@RealyUniqueName](https://github.com/RealyUniqueName) (Compiler Maintainer) 53 | - Justin Donaldson [@jdonaldson](https://github.com/jdonaldson) (Haxe Lua backend) 54 | 55 | To be accepted the HXP needs half + 1 votes in favor of it. 56 | 57 | In order to keep the long term goals and vision of Haxe, Nicolas can veto any accepted proposal after the vote, but will explain his reasoning for doing so in details and agrees to use this power with care. 58 | 59 | If the HXP is accepted, the core team will work on implementing it. 60 | 61 | ## What needs a proposal? 62 | 63 | Stuff that doesn't need a formal proposal (unless it's something fundamental): 64 | 65 | * bugfixes 66 | * optimizations 67 | * documentation 68 | * minor API additions 69 | 70 | Stuff that most probably needs a formal proposal: 71 | 72 | * language changes, including adding new features 73 | * breaking standard library changes 74 | * large standard library additions (new toplevel types are also considered "large") 75 | * significant compiler architecture or build process changes 76 | 77 | Basically, everything that needs some design process and consensus among the developers is a candidate for a proposal. 78 | 79 | ## How to submit an HXP 80 | 81 | 1. Fork the repo, copy the `0000-template.md` to `proposals/0000-short-name.md`, 82 | where `short-name` is a descriptive filename for the proposal document. Don't assign the number yet. 83 | 2. Carefully fill in all sections of the proposal. Pay attention to details: it should show your understanding 84 | of the issue and the impact of the proposed design. 85 | 3. Submit a pull request with the proposal. In this PR, Haxe team and the community can provide 86 | feedback for it. Be prepared to react accordingly and revise the proposal document. 87 | 4. After reaching general consensus or voting takes place, the PR is merged by someone from the Haxe developer team, 88 | and the proposal becomes "active". When merged, the proposal will receive its number from the 89 | corresponding pull request. If the proposal is rejected, the PR is closed with a comment explaining the reasons. 90 | 5. Active proposals are to be further discussed in details by the Haxe developer team 91 | and finally implemented. 92 | 93 | ## Voting process 94 | 95 | It's the responsibility of the author of a proposal to start the voting process. 96 | 97 | Once discussion is exhausted, the author of a proposal can request the voting. 98 | Following requirements should be met: 99 | 100 | * All questions asked by the core members of Haxe Foundation to the author are answered; 101 | * The last edit to a proposal was made at least two weeks ago; 102 | * The last message in the discussion was posted at least two weeks ago. 103 | 104 | To start the voting process the author should post the comment: 105 | 106 | > Request for voting. 107 | 108 | Additionally, the author can tag voters in this comment to draw their attention ([example](https://github.com/HaxeFoundation/haxe-evolution/pull/48#issuecomment-412341110)). 109 | 110 | After this comment is posted the discussion will be locked by any Haxe Foundation member with write access to the haxe-evolution repository. From that point only voters will be able to post comments to the discussion. 111 | -------------------------------------------------------------------------------- /images/hxx-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HaxeFoundation/haxe-evolution/134a34000cc36dc642bf2db030e2575985c3c2db/images/hxx-example.gif -------------------------------------------------------------------------------- /proposals/0001-any.md: -------------------------------------------------------------------------------- 1 | # Any type 2 | 3 | * Proposal: [HXP-0001](0001-any.md) 4 | * Author: [Dan Korostelev](https://github.com/nadako) 5 | * Status: implemented in 3.3.0 6 | 7 | ## Introduction 8 | 9 | Provide new toplevel type `Any` to be used for containing values of any type (hence the name) 10 | as a safer alternative to `Dynamic`. 11 | 12 | ## Motivation 13 | 14 | The current way to pass values of any type is typing them as `Dynamic`, but it's 15 | very error-prone because `Dynamic` completely subverts type-safety and its behaviour 16 | is weird in several ways: 17 | 18 | * Field access to `Dynamic` values yields `Dynamic` values as well, which actually makes sense, 19 | but also further spreads the untyped behaviour which leads to runtime-specific behaviour, like 20 | this famous example: 21 | 22 | ```haxe 23 | var a:Dynamic = [1,2,3]; 24 | a.remove(2); // on e.g. JS we get a run-time error because it doesn't have this field 25 | ``` 26 | 27 | Not to mention that accessing inexistant fields is also runtime-specific and moreover there 28 | can even be no fields at all, if e.g. `Dynamic` value is holding a basic type like an integer. 29 | 30 | * Array access to `Dynamic` values yields monomorph types (`Unknown`) which is inconsistent 31 | with field access, and it also weakens the type-safety of code. 32 | 33 | * `Dynamic` isn't bound to monomorph types which often leads to surprising behaviour when `Dynamic` 34 | is used as a function return type, for example (taken from tink_core README): 35 | 36 | ```haxe 37 | var x = Reflect.field({ foo: [4] }, 'foo'); // x will stay monomorph even though Reflect.field returns Dynamic 38 | if (x.length == 1) // x becomes {+ length : Int } and that doesn't support array access 39 | trace(x[0]); // ERROR: Array access is not allowed on {+ length : Int } 40 | ``` 41 | 42 | This dynamic behaviour might be handy when dealing with the low-level platform specifics but it's completely unnecessary 43 | to bring this dynamicness and weaken type safety in the use case where a simple "value of any type" is needed. 44 | 45 | Since it's not explicit and very easy to mis-use, it makes user code much less safe and unintuitive in these cases 46 | and it disables a lot of compiler features such as static extensions, abstracts methods, extern inline methods, 47 | analyzer optimizations. On a lot of targets, accessing Dynamic fields and operators (such as e.g number comparison) 48 | generates hidden runtime overhead which obviously hurts performance. 49 | 50 | We are telling people to minimize usage of `Dynamic` because of these reasons, but on the other hand we don't 51 | have a good and type-safe way to express a simple fact that a value can be of any type. 52 | 53 | ## Detailed design 54 | 55 | I propose introducing a new type called `Any` that unifies with any other type in both ways, but doesn't provide 56 | any field or array access, doesn't support any operators and is bound to monomorphs like a proper type. 57 | 58 | It serves one purpose - to hold values of any type, but to actually use that values, an explicit casting is required. 59 | That way the code doesn't suddently become dynamically typed and we keep all the static typing goodness, like 60 | advanced type system features and optimizations. 61 | 62 | The proposed implementation is quite simple and doesn't require compiler changes: 63 | 64 | ```haxe 65 | abstract Any(Dynamic) from Dynamic to Dynamic {} 66 | ``` 67 | 68 | This type doesn't make any assumptions about what the value actually is and whether it supports fields or 69 | any operations - this is up to the user. 70 | 71 | Usage: 72 | ```haxe 73 | function setAnyValue(value:Any) { 74 | } 75 | 76 | // value of any type works 77 | setAnyValue("someValue"); 78 | setAnyValue(42); 79 | 80 | 81 | function getAnyValue():Any { 82 | return 42; 83 | } 84 | 85 | var value = getAnyValue(); 86 | 87 | $type(value); // Any, not Unknown<0> 88 | 89 | // won't compile: no dynamic field access 90 | // value.charCodeAt(0); 91 | 92 | if ((value is String)) 93 | // explicit promotion, type-safe 94 | trace((value : String).charCodeAt(0)); 95 | ``` 96 | 97 | ## Impact on existing code 98 | 99 | Adding a new top-level type itself shouldn't break anything. If we implement this, we should promote usage of `Any` 100 | instead of `Dynamic` in places where "any" value is needed, but no dynamic features required. 101 | 102 | We have a couple places in the standard library where Any would be a good choice, like `haxe.Json.parse` and `Unserializer.run`. 103 | Let's change them to return `Any`, but only when a special -D define is active. This will keep compatibility but also provide 104 | an easy way to test and migrate existing code bases to the new concept. Later (in Haxe 4) we could invert the define, or just remove it and use `Any` by default. 105 | 106 | ## Drawbacks 107 | 108 | I don't see any drawbacks: it's a type that solve one exact problem and doesn't introduce any new ones. Its name is 109 | short and means exactly the purpose of the type. Its proposed implementation is simple and non-invasive. 110 | 111 | ## Alternatives 112 | 113 | I was considering stripping down `Dynamic` to have the same meaning as the proposed `Any`, but that has the following disadvantages: 114 | * It's a huge breaking change. 115 | * It requires changing the compiler. 116 | * It complicates low-level target-specific code (e.g. reflection, extern helpers, etc). 117 | * The name `Dynamic` is long and suggests some kind of dynamic behaviour, so it's better suited for the current 118 | version of `Dynamic`. 119 | 120 | ## Opening possibilities 121 | 122 | If we embrace the `Any` type and leave `Dynamic` for the "really dirty low-level stuff", we could not take `Dynamic` into consideration when designing and implementing new type system features, such as method overloading, which is surely going to make life easier. 123 | -------------------------------------------------------------------------------- /proposals/0002-arrow-functions.md: -------------------------------------------------------------------------------- 1 | # Arrow functions 2 | 3 | * Proposal: [HXP-0002](0002-arrow-functions.md) 4 | * Author: [Alexander Kuzmenko](https://github.com/RealyUniqueName) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | Provide better syntax for anonymous functions declaration. 10 | 11 | ## Motivation 12 | 13 | * Even simple closures encourage splitting expressions into multiple lines, which makes code too bloated. 14 | It's hard to read and maintain code that heavily relies on anonymous functions. 15 | ```haxe 16 | //hard to read 17 | var names = users.map(function(u) return u.getProfile()).filter(function(p) return p.inCountry('USA')).map(function(p) return p.getName()); 18 | 19 | //a little better 20 | var profiles = users.map(function(u) return u.getProfile()); 21 | profiles = profiles.filter(function(p) return p.inCountry('USA')); 22 | var names = profiles.map(function(p) return p.getName()); 23 | 24 | //best 25 | var names = users.map(u -> u.getProfile()).filter(p -> p.inCountry('USA')).map(p -> p.getName()); 26 | ``` 27 | 28 | * In some cases total length of boilerplate code may be two times bigger than useful code size. 29 | ```haxe 30 | array.map(function(a) return a.toInt()).sort(function(a, b) return a - b); 31 | //vs 32 | array.map(a -> a.toInt()).sort((a, b) -> a - b); 33 | ``` 34 | Required boilerplate reduced from 32 chars to 4. 35 | 36 | * Nowadays arrow functions are largely adopted by programmers community. 37 | Lack of such functions in Haxe raises questions for newcomers that Haxe suffers stagnation in development. 38 | This factor has negative impact on growth of Haxe community. 39 | 40 | * Current syntax of anonymous functions in Haxe is quite verbose compared to other languages. 41 | ```haxe 42 | //Haxe 43 | function() return expr 44 | function(arg) return expr 45 | 46 | //TypeScript 47 | () => expr 48 | arg => expr 49 | 50 | //JavaScript 51 | () => expr 52 | arg => expr 53 | 54 | //C# 55 | () => expr 56 | arg => expr 57 | 58 | //Scala 59 | () => expr 60 | (arg) => expr 61 | 62 | //Java 63 | () -> expr 64 | (arg) -> expr 65 | 66 | //Swift 67 | { expr } 68 | { arg1 in expr } 69 | 70 | //Ruby 71 | ->() {expr} 72 | ->(arg) {expr} 73 | 74 | //And so on... 75 | ``` 76 | 77 | ## Detailed design 78 | 79 | ### Syntax 80 | 81 | Following syntax is proposed for arrow functions in Haxe: 82 | 83 | * No arguments: 84 | 85 | ```haxe 86 | () -> expr 87 | //equivalent for 88 | function() return expr; 89 | ``` 90 | 91 | * Single argument (parentheses are optional): 92 | 93 | ```haxe 94 | arg -> expr 95 | (arg) -> expr 96 | //equivalent for 97 | function(arg) return expr; 98 | ``` 99 | 100 | * Multiple arguments: 101 | 102 | ```haxe 103 | (arg1, arg2) -> expr 104 | //equivalent for 105 | function(arg1, arg2) return expr; 106 | ``` 107 | 108 | * Explicit typing: 109 | 110 | ```haxe 111 | (arg1:Int, arg2:String) -> expr 112 | //equivalent for 113 | function(arg1:Int, arg2:String) return expr; 114 | ``` 115 | Explicit return type for arrow functions should not be allowed to not interfer with existing syntax of function type. 116 | 117 | ### AST 118 | 119 | Since proposed syntax is just another way to express function declaration, no AST changes required. 120 | 121 | But in Haxe 4.0 new field `@:optional isLambda:Bool` could be added to [haxe.macro.Function](http://api.haxe.org/haxe/macro/Function.html) structure to distinct normal and arrow function syntaxes in AST. 122 | 123 | ## Impact on existing code 124 | 125 | Arrow functions will have no impact on existing code. 126 | 127 | It's a compile-time feature so it will not affect runtime. 128 | 129 | ## Drawbacks 130 | 131 | No drawbacks. 132 | 133 | ## Alternatives 134 | 135 | Since users got used to use arrow functions in other languages, various implementations were created with macros: at least two librarys on haxelib 136 | and few more projects in outher sources. 137 | However such implementations impact compilation time, introduce non-standart (and different) syntax and should be avoided inside of other macros (breaks compiler cache) 138 | -------------------------------------------------------------------------------- /proposals/0003-new-function-type.md: -------------------------------------------------------------------------------- 1 | # New function type syntax 2 | 3 | * Proposal: [HXP-0003](0003-new-function-type.md) 4 | * Author: [Dan Korostelev](https://github.com/nadako) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | Provide a new, more natural syntax for declaring function types with support for argument names. 10 | 11 | ## Motivation 12 | 13 | Haxe supports first-class functions from the beginning, using the following syntax for type-hinting variables containing functions: 14 | 15 | ```haxe 16 | Int -> String -> Void 17 | ``` 18 | 19 | This syntax is often found in functional languages, such as OCaml and Haskell, but much less in non-functional and hybrid languages. 20 | 21 | There are several issues with this syntax: 22 | 23 | * For people familiar with functional programming languages, it suggests that auto-currying and partial application are supported, but they aren't. 24 | * For non-FP people, it looks unfamiliar and differs too much from the actual function definition syntax. 25 | * It doesn't support argument names. While they aren't important for the type system, they are very useful for self-documenting code, IDE signature hints, callback auto-generation, etc. (see [fancy examples with screenshots here](https://github.com/HaxeFoundation/haxe/pull/6428#issue-239976019)) 26 | 27 | What we could have instead is a function type syntax that follows the new [arrow function](https://github.com/HaxeFoundation/haxe-evolution/blob/master/proposals/0002-arrow-functions.md) syntax: 28 | 29 | ```haxe 30 | (id:Int, name:String) -> Void 31 | ``` 32 | 33 | ## Detailed design 34 | 35 | ### Syntax 36 | 37 | The proposed syntax would looks like this: 38 | 39 | ```haxe 40 | // no arguments 41 | () -> Void 42 | 43 | // single argument 44 | (name:String) -> Void 45 | 46 | // multiple (also, optional) arguments 47 | (name:String, ?age:Int) -> Void 48 | 49 | // unnamed arguments 50 | (Int, String) -> Bool 51 | 52 | // mixed arguments, why not 53 | (a:Int, ?String) -> Void 54 | ``` 55 | 56 | This is a rather small parser change, adding additional rules after the `(` token in `parse_complex_type` routine. 57 | 58 | ### Representation 59 | 60 | There are different approaches regarding representation of named arguments in the AST: 61 | 62 | * Add a new `complex_type` variant: `CTNamed` for representing `name:Type` part and use that for `CTFunction` argument list. This would be similar to `CTOptional`. 63 | * Rework `CTFunction` arguments structure to contain a list of `(name, opt, type)` tuples, having empty string for names when the value comes from parsing unnamed arguments or old function type syntax. 64 | 65 | Both options are viable and both will require changing macro data structures (which is breaking), so this is something we should discuss. 66 | 67 | ### Interoperability with the old syntax 68 | 69 | Old syntax stays in place and works like before, examples are: 70 | 71 | ```haxe 72 | Int -> Int 73 | (Int) -> Int 74 | (Int) -> Int -> Int 75 | (Int -> Int) -> Int -> Int 76 | // etc. 77 | ``` 78 | 79 | Mixing old and new syntax without parenthesis results in a syntax error: 80 | 81 | ```haxe 82 | (Int, Int) -> Int -> Int // syntax error: unexpected -> 83 | (a:Int) -> Int -> Int // same 84 | ``` 85 | 86 | If the desired behaviour is to have a functional return type, parenthesis should be used: 87 | ```haxe 88 | (Int, Int) -> (Int -> Int) 89 | (a:Int) -> (Int -> Int) 90 | ``` 91 | 92 | ## Impact on existing code 93 | 94 | Depending on how we implement AST data structures (options are listed in the [Representation](#representation) section), this will potentionally break macros that work with `haxe.macro.Expr.ComplexType` in one way or another, other than that it should not break anything because it's a completely new syntax. 95 | 96 | ## Drawbacks 97 | 98 | The obvious drawback is that we'll have two function type syntaxes, which is why I think we should deprecate and eventually remove the old syntax. Removing the old syntax would be a huge change, but could still be an option for a major release, especially if we provide a migration tool. 99 | 100 | ## Alternatives 101 | 102 | I already [tried the another approach](https://github.com/HaxeFoundation/haxe/pull/6428) of augmenting the current funtion type syntax with argument names: `a:Int -> b:String -> Void`, but unfortunately that introduced a [syntax ambiguity](https://github.com/HaxeFoundation/haxe/issues/6433) with macro reification and case patterns. One workaround for that would be to require parenthesis for named arguments, so it would be `(name:Type) -> Ret`, but that [suggests](https://github.com/HaxeFoundation/haxe/pull/6428#issuecomment-312671102) that one could do `(name:Type, name:Type) -> Ret` while it's an invalid syntax (one would have to do `(name:Type) -> (name:Type) -> Ret` instead). 103 | 104 | ## Unresolved questions 105 | 106 | * `ComplexType` representation for named arguments 107 | * Removal of old function type syntax 108 | -------------------------------------------------------------------------------- /proposals/0004-intersection-types.md: -------------------------------------------------------------------------------- 1 | # Intersection types 2 | 3 | * Proposal: [HXP-0004](0004-intersection-types.md) 4 | * Author: [Simn](https://github.com/simn) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | Introduce the notion of intersection types using a `Type1 & Type2` syntax. 10 | 11 | 12 | ## Motivation 13 | 14 | There are currently two cases in the Haxe language which support some notion of intersection types: 15 | 16 | ### Structural Extensions 17 | 18 | ```haxe 19 | typedef Type1 = { 20 | var field1:String; 21 | } 22 | 23 | typedef Type2 = { 24 | var field2:Int; 25 | } 26 | 27 | typedef Type3 = { 28 | > Type1, 29 | > Type2, 30 | var field3:Bool; 31 | } 32 | ``` 33 | 34 | Here, `Type3` is the intersection of types `Type1` and `Type2`. It contains all fields of `Type1` plus all fields of `Type2`. 35 | 36 | While the functionality is fine (and would be adopted by this proposal), the syntax is somewhat curious. This would be replaced by the following: 37 | 38 | ```haxe 39 | 40 | // Type1 and Type2 remain the same 41 | 42 | typedef Type3 = Type1 & Type2 & { 43 | var field3:Bool; 44 | } 45 | ``` 46 | 47 | Since the order of types does not matter, this can be formatted in various ways, such as: 48 | 49 | ```haxe 50 | typedef Type3 = { 51 | var field3:Bool; 52 | } & Type1 & Type2; 53 | 54 | typedef Type3 = 55 | Type1 & 56 | Type2 & { 57 | var field3:Bool; 58 | } 59 | ``` 60 | 61 | ### Type parameter constraints 62 | 63 | Haxe allows constraining type parameters to multiple types at once: 64 | 65 | ```haxe 66 | class Class { } 67 | ``` 68 | 69 | This reads: If a type is used in place of `Param`, it must unify with `Type1` and it must unify with `Type2`. Logically, this could also be expressed as "it must unify with (`Type1` and `Type2`)". 70 | 71 | As before, the functionality is fine, but the syntax is not. In fact, it even introduces [syntactic ambiguities](https://github.com/HaxeFoundation/haxe/issues/7006) which are hard to understand. We propose to use this instead: 72 | 73 | ```haxe 74 | class Class { } 75 | ``` 76 | 77 | This also happens to be the syntactic version of "it must unify with (`Type1` and `Type2`)" mentioned above. 78 | 79 | 80 | ## Detailed design 81 | 82 | ### Parser 83 | 84 | After parsing a type-hint, we accept a `&` token and then expect another type-hint. The resulting type-hint would then be a new variant to `complex_type`, `CTIntersection of type_hint * type_hint`. By doing this recursively, we support any number of type-hint operands. 85 | 86 | ### Typer 87 | 88 | When loading a `CTIntersection`, we restrict it like so: 89 | 90 | * As long as all operands are structure types or resolve to structure types via typedefs, we create the intersection type like we currently do for `CTExtend`. 91 | 92 | * When typing a type parameter constraint, we resolve all operands of `CTIntersection` individually and recursively. This results in a list of types, which is used for constraints checking like the current constraints list is. 93 | 94 | * In any other case, we emit an error stating that intersection types cannot be used like that. This may change in the future, but for now the goal is to achieve parity with the current feature set. 95 | 96 | It should be noted that the first item in this list takes priority. That is, if we have a type parameter constraint to a structural intersection, we unify once against the intersection, not multiple times against its operands. 97 | 98 | 99 | ## Impact on existing code 100 | 101 | Structural extension syntax would be deprecated but still work. However, the current type parameter constraints syntax should be removed, which breaks code using it. 102 | 103 | The introduction of a new variant to `complex_type` (`ComplexType`) might require some macros to add additional patterns. 104 | 105 | For better backward compatibility, `TypeParamDecl.constraints` can remain an `Array` which will only ever have 0 or 1 entries. 106 | 107 | 108 | ## Drawbacks 109 | 110 | I have to implement it. 111 | 112 | 113 | ## Alternatives 114 | 115 | Instead of this rather general approach, the individual problems of structural extensions and type parameter constraints could be looked into: 116 | 117 | * The [type parameter constraints syntax conflict](https://github.com/HaxeFoundation/haxe/issues/7006) would have to be addressed, likely breaking it anyway. 118 | 119 | * The syntax for structural extensions should be reviewed. It [shouldn't require a `,`](https://github.com/HaxeFoundation/haxe/issues/7036) and should probably support `;` for class field notation as well. 120 | 121 | * The result of [unifying against structural extension components](https://github.com/HaxeFoundation/haxe/issues/5225) should be reviewed because the current behavior is not in-line with the overall unification behavior. 122 | 123 | 124 | ## Opening possibilities 125 | 126 | Since the goal for now is only feature parity, it doesn't open any new possibilities outside of macros. 127 | 128 | In the future, we can support intersection types for interfaces as well. If you have lots of classes which use `implements Interface1 implements Interface2` (and potentially many more), you could instead `typedef Interface3 = Interface1 & Interface2` and use `implements Interface3`. This would work exactly like before, but be easier to write and maintain, especially in interface-heavy code. 129 | 130 | ## Unresolved questions 131 | 132 | None! 133 | -------------------------------------------------------------------------------- /proposals/0005-key-value-iter.md: -------------------------------------------------------------------------------- 1 | # key => value iteration syntax 2 | 3 | * Proposal: [HXP-0005](0005-key-value-iter.md) 4 | * Author: [Dan Korostelev](https://github.com/nadako) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | Support easy iteration over key-value pairs in the `for` loop syntax. 10 | 11 | (see also: https://github.com/HaxeFoundation/haxe/issues/2421) 12 | 13 | ## Motivation 14 | 15 | Very often when iterating over various collections we want to have both element key/index and its value 16 | as local variables. Right now, there's no special syntax for that in Haxe, so we have to iterate over 17 | keys/indices and extract the value from the collection at each step. This makes code less concise and 18 | declarative and thus more error-prone: 19 | 20 | ```haxe 21 | for (key in map.keys()) { 22 | var value = map.get(key); 23 | trace(key, value); 24 | } 25 | 26 | for (index in 0...array.length) { 27 | var value = array[index]; 28 | trace(index, value); 29 | } 30 | ``` 31 | 32 | What we could have instead is this: 33 | 34 | ```haxe 35 | for (key => value in map) { 36 | trace(key, value); 37 | } 38 | 39 | for (index => value in array) { 40 | trace(index, value); 41 | } 42 | ``` 43 | 44 | ## Detailed design 45 | 46 | ### Syntax 47 | 48 | As shown in the "Motivation" section above, the proposed syntax would be `for (key => value in collection)`. 49 | This syntax seems logical and consistent with the current map declaration syntax (`[key => value]`). 50 | 51 | It's also not a breaking change, because at the moment the only allowed AST node before the `in` is a simple identifier. 52 | 53 | ### Semantics 54 | 55 | For this to work, we introduce a new standard iterable type: 56 | 57 | ```haxe 58 | typedef KeyValueIterator = Iterator<{key:K, value:V}>; 59 | 60 | typedef KeyValueIterable = { 61 | function keyValueIterator():KeyValueIterator; 62 | } 63 | ``` 64 | 65 | When typing the `for (key => value in collection) {}` expression, we handle it in a similar way as normal iterators, that is: 66 | 67 | 1) if `collection` conforms to `KeyValueIterator`, generate: 68 | ```haxe 69 | while (collection.hasNext()) { 70 | var tmp = collection.next(); 71 | var key = tmp.key; 72 | var value = tmp.value; 73 | } 74 | ``` 75 | 76 | 2) if `collection` conforms to `KeyValueIterable`, generate: 77 | ```haxe 78 | var iterator = collection.keyValueIterator(); 79 | while (iterator.hasNext()) { 80 | var tmp = iterator.next(); 81 | var key = tmp.key; 82 | var value = tmp.value; 83 | } 84 | ``` 85 | 86 | 3) otherwise, emit the `Type has no field keyValueIterator` error 87 | 88 | 89 | ### Performance 90 | 91 | For the most common case (`for` loop), if implemented properly, key-value iterators will be fully 92 | inlined and temp variables will be optimized away, so it should be at least as good as hand-written 93 | code similar to the one in the "Motivation" section. For example, see [this try.haxe snippet](http://try-haxe.mrcdk.com/#9c3Aa). 94 | 95 | For some specific collection implementations, I imagine the key/value iterator could be faster than 96 | iterating over keys and getting the value each time, if the collection can provide pairs directly. 97 | Actually, even standard `Array` might benefit from this, since it could save some bounds checking on getting values. 98 | 99 | One performance-related concern is the use of structural typing, which can be an issue on static 100 | targets in their current implementation, however this is a more general problem which also applies to 101 | normal iterators, so it's out of scope for this proposal. Still, we might want to provide a standard 102 | optimized implementation of the readonly `KeyValuePair` type implementation that would be used for key/value iterators 103 | instead of `{key:K, value:V}`. 104 | 105 | ## Impact on existing code 106 | 107 | This shouldn't break much: the `key => value` before `in` within `for` is currently forbidden, 108 | and it [doesn't seem](https://github.com/search?l=&q=keyValueIterator+language%3AHaxe&ref=advsearch&type=Code&utf8=%E2%9C%93) like there's any functions named `keyValueIterator` in public Haxe code. 109 | 110 | ## Alternatives 111 | 112 | No real alternative. Of course, one could macro-process every `for` loop with a global `@:build` macro, 113 | but that would be an overkill for such simple feature. 114 | -------------------------------------------------------------------------------- /proposals/0006-inline-markup.md: -------------------------------------------------------------------------------- 1 | # Inline markup 2 | 3 | * Proposal: [HXP-0006](0006-inline-markup.md) 4 | * Author: [Juraj Kirchheim](https://github.com/back2dos) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | This is an attempt to salvage the rejected [Inline XML proposal](https://github.com/HaxeFoundation/haxe-evolution/pull/12). 10 | The idea is to allow the compiler to identify "inline markup literals" as *opaque* expressions that can be processed by macros, with "markup" actually being as vague as possible. To illustrate what constitutes such inline markup: 11 | 12 | 1. For those who want JSX: 13 | 14 | ```haxe 15 | funtion doThatReactThing() 16 | return
17 |
18 | Click me! 19 |
20 |
21 | ``` 22 | 2. HEREDOC-like syntax has been requested for a long time now, and inline markup would allow to embed arbitrarily complex strings into haxe: 23 | 24 | ```haxe 25 | var myComplexString = 26 | 27 | In here I can write "anything" without the parser really interfering 28 | 29 | ``` 30 | 3. Or you know, just go crazy: 31 | 32 | ```haxe 33 | function madness() 34 | 35 | console.log("This is Spartaaaaaaa!!!"); 36 | 37 | ``` 38 | 39 | 4. And while you're at it, mix it any way you want it: 40 | 41 | ```haxe 42 | function someHTML() 43 | return 44 | 45 | Example 46 | 47 | 48 | 49 | 54 |

Hello, world!

55 |
Edit me!
56 | 59 | 60 | 61 | ``` 62 | 63 | In each of those cases, Haxe is blisfully unaware of any language carnage going on *within* inline markup. It knows only enough to identify runs of inline markup, so that macros can pick them up and parse them. If an inline markup literal is not processed by any macro and makes it all the way to the typer, an error is generated (could just be "Unprocessed inline markup"). 64 | 65 | ## Motivation 66 | 67 | The main motivation here is really to make Haxe more appealing to web and desktop/mobile application developers. For better or worse, XML-ish dialects have establised themselves as the de-facto lingua franca of describing UIs for both web and native environments. XIB/Storyboard, XAML, MXML, FXML, JSX, HTML, HaxeUI markup, Stablex markup, ... you name it. If we want Haxe to gain traction outside game development, working on tighter integration between UI markup and the rest of the language is not a "nice to have" but of pivotal importance. 68 | 69 | That said this proposal aims to be a basis that opens many possibilities instead of comitting Haxe to support any single hype, thus giving macro authors the freedom of choosing whichever sinking ship they want to put their bets on. 70 | 71 | ## Detailed design 72 | 73 | If wherever an expression is expected to begin, the character `<` is found followed *directly* (i.e. no whitespace inbetween) by a letter, it signifies the start of an inline markup expression. Then the opening "tag" is determined (this will be something of the form ``), while continuing the search if that tag was not balanced. Alternatively, if there is no `<` and `>` between the opening tag and the next `/>`, then we extract that text run as a markup literal. In Haxe code: 74 | 75 | ```haxe 76 | var start = parser.pos;//which is at this point the position of the left angular bracket preceeding the letter 77 | var parseTagName = ~/^[-._:a-zA-Z0-9]+/; 78 | parseTagName.matchSub(source, start + 1); 79 | var tagName = parseTagName.matched(0); 80 | var openingTag = '<$tagName', 81 | closingTag = ''; 82 | 83 | parser.pos = start + openingTag.length; 84 | 85 | //below `hasNeither` and `count` are left as an excercise to the reader 86 | 87 | switch source.indexOf('/>', parser.pos) { 88 | case v if (v != -1 && hasNeither(source, parser.pos, v, ['<', '>'])): 89 | parser.pos = v + 2; 90 | default: 91 | do { 92 | switch source.indexOf(closingTag, parser.pos) { 93 | case -1: throw 'unclosed $openingTag'; 94 | case v: parser.pos = v + closingTag.length; 95 | } 96 | } while (count(openingTag, source, start, parser.pos) != count(closingTag, source, start, pos)); // 97 | } 98 | 99 | return makeInlineMarkupExpression(source, start, parser.pos);//just turn it into some opaque expression 100 | ``` 101 | 102 | The result here would either be an `EConst(CMarkup(theMarkup))` or a `EMeta(':markup', EConst(CString(theMarkup)))` if we wish to avoid breaking changes to the AST. To repeat the above: **unprocessed markup literals should be rejected by the typer**. 103 | 104 | It has been suggested to have a default meaning, but I maintain for now that it is practically impossible to find one that will work well for all use cases and even if it is possible, it can be put forward in a follow-up proposal that can be implemented at a later time. 105 | 106 | ## Impact on existing code 107 | 108 | This feature does not impact existing non-macro code directly. Depending on how it is encoded as an expresion, it may break macros. 109 | 110 | ## Drawbacks 111 | 112 | 1. The proposal may be somewhat modest, because it still means that people have to write macros to process the inline markup. 113 | 2. Cases can be constructed with CDATA and comments that violate the principle of least surprise: 114 | 115 | ```haxe 116 | var xml = ]]>;//will result in "expected ]" 117 | var xml = ;//will result in "unexpected >" 118 | ``` 119 | I'm going to go with: true, these cases exist, but they don't matter. There are countless ways to deal with the issue should it arise. 120 | 121 | ## Alternatives 122 | 123 | 1. Parse external files at macro time. This is pretty neat, but one of the biggest problems here is that you just can't convince the compiler to give you autocompletion. And you can't blame it, because it would have to type the whole codebase to figure out which macro is actually causing the file to be parsed and call it accordingly. 124 | 125 | 2. Parse inline strings. This works *amazingly well* in `tink_hxx` (I am biased here, but I think it really shows the benefits of a proper, tight integration): 126 | 127 | ![](../images/hxx-example.gif) 128 | 129 | However, trying to get the parser to be reentrant (so you can write e.g. `
{[1,2,3].map(i -> )}
`), requires you to parse Haxe syntax by hand, which not only is slow and desparate and silly, but also kills autocompletion ... unless you want to implement that part too, which is way beyond desparate and silly. 130 | 131 | 3. The idea was proposed to make such a language extension rely on compiler plugins. I'm not even sure it is possible to swap out the parser in this manner, but I would argue it is undesirable, because it makes all libraries that rely on custom parsers mutually exclusive. This is not so with macros, because macros are called explicitly through specific entry points and can therefore coexist in the same build. 132 | 133 | ## Opening possibilities 134 | 135 | It has been pointed out that [Haxe could greatly benefit from an easy way to embed *any* (domain specific) language](https://github.com/HaxeFoundation/haxe-evolution/pull/12#issuecomment-306733033). This proposal happens to do that. It is fair to ask whether the surrounding delimiters need to be "tags". They don't: it could be any ASCII-art. However using tags has two advantages: 136 | 137 | 1. It just so happens that most people are used to tags acting as delimiters, so we don't have to reinvent the wheel here. 138 | 2. If the embedded language is XML-ish, then the delimiter fuses with the language, which reduces visual clutter. 139 | 140 | ## Unresolved questions 141 | 142 | An important question is how to go about syntax highlighting within inline markup, but it seems to me that it doesn't concern the language as much as it concerns IDEs. 143 | -------------------------------------------------------------------------------- /proposals/0007-module-level-funcs.md: -------------------------------------------------------------------------------- 1 | # Module-level functions and variables 2 | 3 | * Proposal: [HXP-0007](0007-module-level-funcs.md) 4 | * Author: [Dan Korostelev](https://github.com/nadako) 5 | * Status: [implemented in Haxe 4.2.0](https://github.com/HaxeFoundation/haxe/pull/8460) 6 | 7 | ## Introduction 8 | 9 | Support defining `function`s and `var`s directly in the module (.hx file) instead of creating a class with static fields. 10 | 11 | ## Motivation 12 | 13 | Classes are the heart of object-oriented programming found in languages like Java. In this paradigm, we model our program as interaction between class instances, so it makes sense to have classes as containers for everything. Non-factory static methods are relatively rare, so there's no practical need for functions defined outside of a class. 14 | 15 | Nowadays, however, other programming paradigms, such as functional programming are becoming more and more popular, reducing the need for classic OOP classes and instead focusing more on functions that process passive data structures. 16 | 17 | Haxe provides a lot of features supporting functional oriented paradigms (most importantly first-class functions), however it lacks a clean way to actually define functions without creating a wrapping class. This is annoying and gives a feeling of bloatedness to new people coming from non-OOP background. This is particularly unfortunate, because most of our target languages support plain functions, so having to wrap everything in a class can be a con when deciding whether to use Haxe or a target language directly. 18 | 19 | ## Detailed design 20 | 21 | Supporting module-level functions should be pretty-straightforward. To minimize changes in compiler and its data structures, as well as the macro API, I propose the following: 22 | 23 | * add `TDFunction(name:String, fun:Function)` case to the `TypeDefKind` enum. 24 | * allow parsing functions at the module level and parse them into that `TDFunction`. 25 | * on module loading, when processing syntax declarations into module types, treat all module-level functions as `public static` methods of an implicitly created class. For this class we introduce a new `ClassKind` variant: `KModuleStatics` or something. This is very similar to how `KAbstractImpl`-classes are implicitly created for abstract types. 26 | * thereafter, when resolving an identifier (see more below), actually generate a static field access (`TTypeExpr(ModuleStatics).static(name)`). 27 | * when generating output, if target supports declaring plain functions (JavaScript, Lua, C++, etc.), a generator can decide to lose the wrapping `KModuleStatics` class and generate functions directly. If target requires a wrapping class (Java, C#) - generate like a normal class (plus, some optimizations can be applied, e.g. C# could mark class as `static`, and don't generate reflection helpers). 28 | 29 | While the default access modifier for module-level functions is `public`, private module-level functions are supported by explicitly specifying the `private` keyword. 30 | 31 | While this proposal describes module-level functions, vars are implemented the same with a `TDVar` variant. Properties can be supported too. 32 | 33 | ### Identifier resolution 34 | 35 | The idea of module-level identifiers doesn't play particularly well with our current static field resolution mechanism, because we already have the concept of "primary module class" (a class with the same name as the module), so the most logical and least intrusive way to implement this would be to simply collect module-level functions in a implicitly created primary class and forbid explicit primary class declaration when there are module-level functions or vars. 36 | 37 | Importing a module with `KModuleStatics` primary class should also imply `import ModuleName.*`, which means that all module-level functions/vars are imported by importing the module, which I believe would be the expected behaviour. 38 | 39 | ### Code example 40 | 41 | Here's some code to break down the wall of text a bit. A slightly complicated hello-world command line script would look something like this: 42 | 43 | Hello.hx 44 | ```haxe 45 | inline var USAGE = "Usage: hello "; 46 | 47 | typedef Config = { 48 | name:String, 49 | times:Int, 50 | } 51 | 52 | function sayHello(config:Config) { 53 | for (i in 0...config.times) 54 | trace('Hi, ${config.name}!'); 55 | } 56 | 57 | function main() { 58 | var args = Sys.args(); 59 | if (args.length != 2) 60 | trace(USAGE); 61 | else 62 | sayHello({name: args[0], times: Std.parseInt(args[1])}); 63 | } 64 | ``` 65 | 66 | As you can see, mixing module-level functions and vars with other type declarations (`typedef Config` here) works just fine. 67 | 68 | ### Reflection 69 | 70 | Since the module-level functions/vars end up being static class fields, the usual reflection should automatically work (e.g. `Reflect.field(Type.resolveClass("MyModule"), "myMethod")`). Actually I'm not sure if this is specified to work currently in Haxe, but if it does, generators should respect that and don't over-optimize `KModuleStatics` generation when reflection features are enabled. 71 | 72 | ### Macros, static extensions and so on 73 | 74 | Since, again, module-level functions/vars are actually static fields, usual rules for `using` and `@:build(MyModule.myMethod)` calls apply, nothing new here. 75 | 76 | ### Final note 77 | 78 | Because this document proposes implementing module-level functions in form of static class fields, 79 | one may think that it would be inconsistent to have them public and imported implicitly with the module, remember however that this proposal is not about providing syntax sugar for static fields, but about defining functions and var on module level, and the fact they become static fields is an _implementation detail_. 80 | 81 | Accessibility and resolution should follow those of other module-level declarations, that is: a module-level declaration (e.g. class/typedef/etc) is public unless specified as private, all module-level declarations are imported when the module is imported. Following these rules it makes sense to have module-levels functions to be public and be imported by default. 82 | 83 | ## Impact on existing code 84 | 85 | With regard to existing code, this change can only potentially affect macro code because of newly introduced enum constructors in the macro API, and I believe that a very small portion of macro code will be affected by this, because it only matters for exhaustive pattern matches on `TypeDefKind` and `ClassKind` which are quite rare. 86 | 87 | ## Drawbacks 88 | 89 | I don't immediately see any drawbacks in the proposed feature. On the contrary, I believe it'll make Haxe not only more competitive in terms of expressiveness in everyday use, but also easier to learn for absolute beginners in programming, because they won't have to learn the concept of a class and static methods from the start. 90 | 91 | ## Alternatives 92 | 93 | I don't see any viable alternatives that would allow defining plain functions. Having a Haxe superset that is compiled to Haxe with a macro or in any other way isn't something anyone would seriously consider in practice. 94 | 95 | ## Opening possibilities 96 | 97 | I think we could also use module-level functions to define extern functions, e.g. 98 | ```haxe 99 | extern function SDL_Init(flags:UInt32):Int; 100 | ``` 101 | 102 | But that's gonna require some more thought, because it would mean that the implicitly created class must be made extern automatically. 103 | 104 | ## Unresolved questions 105 | 106 | None so far. 107 | -------------------------------------------------------------------------------- /proposals/0008-macro-forward.md: -------------------------------------------------------------------------------- 1 | # Expression macro method forwarding 2 | 3 | * Proposal: [HXP-0008](0008-macro-forward.md) 4 | * Author: [Dan Korostelev](https://github.com/nadako) 5 | * Status: [superseded](https://github.com/HaxeFoundation/haxe/pull/11553) 6 | 7 | ## Introduction 8 | 9 | Provide a way to have expression macro functions inside a class without typing that whole class 10 | in macro context. 11 | 12 | ## Motivation 13 | 14 | It's often desirable to have a macro function inside a "normal" class/abstract to achieve 15 | nice API. 16 | 17 | However, doing this will cause the module containing a macro function to be also typed 18 | in the macro context, even if the macro function doesn't use anything from that module. 19 | 20 | This in turn can lead to annoying and surprising errors, since the other code in that 21 | module wasn't written with macros in mind and can depend on APIs that aren't even available 22 | in macros (e.g. target-specific ones). 23 | 24 | For example: 25 | ```haxe 26 | class Main { 27 | static function main() { 28 | js.Browser.console.log("Version " + getVersion()); 29 | } 30 | 31 | static macro function getVersion() { 32 | return MyMacroTools.getVersionExpr(); 33 | } 34 | } 35 | ``` 36 | This will fail with `You cannot access the js package while in a macro (for js.Browser)` 37 | without even specifying the actual cause of the error. 38 | 39 | And even if it does pass through the typer, most of the code will simply be 40 | unused in macro context, so it's just a needless work consuming precious time. 41 | 42 | The current solution is to fence the non-macro code (and imports) with `#if !macro`, 43 | which hurts readability and often confuses people not familiar with macros, causing 44 | them to "wtf" by accidentally adding a function outside of `#if !macro` region. 45 | 46 | What I'd like to propose is a way to avoid the very cause of the issue by forwarding 47 | macro calls into another module. 48 | 49 | ## Detailed design 50 | 51 | The proposed solution is quite simple: allow macro methods without body if they have 52 | a special metadata pointing to the static method in another type. For example, the function 53 | from the example above could be defined like this instead: 54 | 55 | ```haxe 56 | @:forwardMacro(MyMacroTools.getVersionExpr) 57 | static macro function getVersion(); 58 | ``` 59 | 60 | When typing a macro-call, if the macro method has no body, the compiler would look 61 | for that metadata and extract the path to a real method to be evaluated. So if that 62 | real method is in another module, the current one doesn't need to be typed in macro 63 | context at all. 64 | 65 | In addition to per-method macro we could have class-level `@:forwardMacro(MyMacroClass)` 66 | metadata that will forward all bodyless macro methods from this class to the specified `MyMacroClass`. 67 | 68 | ## Impact on existing code 69 | 70 | This should have no impact on existing code whatsoever, because it doesn't bring any 71 | new syntax or change any existing behaviour. 72 | 73 | ## Drawbacks 74 | 75 | One could argue that very simple macro methods should rather be in a class along 76 | with the others, however in practice, macro code is never that simple and almost 77 | always deserves to be separated. And anyway, it'll be still possible to do using 78 | good old `#if !macro` fencing. 79 | 80 | ## Opening possibilities 81 | 82 | While not really related, there's another macro-related proposal I'd like to prepare 83 | in near future about having "macro modules", which should play well with the method 84 | forwarding functionality. 85 | 86 | -------------------------------------------------------------------------------- /proposals/0009-inline-calls.md: -------------------------------------------------------------------------------- 1 | # Inlining functions at call location 2 | 3 | * Proposal: [HXP-0009](0009-inline-calls.md) 4 | * Author: [YellowAfterlife](https://github.com/yellowafterlife) 5 | * Status: implemented in 4.0.0 6 | 7 | ## Introduction 8 | 9 | Provide new syntax for requesting functions to be inlined at call location rather than place of definition. 10 | 11 | ## Motivation 12 | 13 | This is a pretty common thing, even for [standard library](https://github.com/HaxeFoundation/haxe/blob/development/std/haxe/io/Input.hx#L229-L247) - you have a pair of functions of similar purpose and one does what other does plus a little extra (like handling sign bit in that case). Or have functions that are actively used inside of other functions (potentially benefiting from being inline there) while being used normally from "external" code. 14 | 15 | But you cannot - a function is either inline (hinted or forced with @:extern) or it isn't. 16 | 17 | ## Detailed design 18 | 19 | Considering the current syntax, I think it would be fitting to have this as `inline ` prefix "operator" - so this would permit to write the earlier shown code like 20 | ```haxe 21 | public function readInt16() : Int { 22 | var n = inline readUInt16(); 23 | if( n & 0x8000 != 0 ) ... 24 | ``` 25 | If the function cannot be inlined, an error could be shown as it is with `@:extern inline`. 26 | 27 | ## Impact on existing code 28 | 29 | `inline` is not currently used in expression syntax at all and match rule is pretty clear so there shouldn't be any unexpected behaviour. 30 | 31 | ## Drawbacks 32 | 33 | Documentation would need to note that the function inlined would be the one observed by the compiler/`$type`. This is fairly obvious and is an advantage (as you can have a base class methods share code without worrying about things getting strange if shared function is later overriden in a child class), but to be sure... 34 | 35 | ## Alternatives 36 | 37 | Currently the best you can do is making a separate "this one function but inline" function, then have the "normal" version to call the inline version, and have other functions call either normal or inline version. 38 | 39 | ## Opening possibilities 40 | 41 | If later allowing to use this with instantiation (TNew), the combination of two could be used to inline small "normal" classes in places of intensive use without having to deal with garbage collection or pooling. This doesn't have alternatives aside of duplicating classes or some [delightful hacks](https://try.haxe.org/#Ee0DC) (having a function to prevent inlining). 42 | -------------------------------------------------------------------------------- /proposals/0010-asys.md: -------------------------------------------------------------------------------- 1 | # New `asys` APIs 2 | 3 | * Proposal: [HXP-0010](0010-asys.md) 4 | * Author: [Aurel Bílý](https://github.com/Aurel300) 5 | * Status: [to be implemented](https://github.com/Aurel300/haxe-sys) 6 | 7 | ## Introduction 8 | 9 | Improved API for both synchronous and asynchronous filesystem operations; improved networking API; improved threading and process API; asynchrony primitives; I/O streams. 10 | 11 | ## Motivation 12 | 13 | ### Asynchrony 14 | 15 | There is currently no good way to asynchronously perform many `sys`-related tasks (without manually creating `Thread`s). Two basic primitives are added to the library: 16 | 17 | - [signals](#signals) (and listeners) 18 | - [unified callback style](#callbacks) 19 | 20 | ### Streams 21 | 22 | The current Haxe API contains `haxe.io.Input` and `haxe.io.Output` for input and output streams. These lack: 23 | 24 | - ability to express a read **and** write stream (`sys.io.File` has two separate streams rather than one RW stream) 25 | - pipelining without manual chunking 26 | - proper asynchronous operations 27 | - automatically pacing streams with different data emission / consumption rates 28 | 29 | ### Filesystem 30 | 31 | The current filesystem APIs in Haxe lack a number of important features: 32 | 33 | - asynchronous tasks 34 | - changing permissions, owners of files 35 | - symlink operations 36 | - watching for changes 37 | 38 | ### Networking 39 | 40 | Non-blocking socket operations are inconvenient to use in the current API even though they are the only (non-`Thread`) solution to some real-time network communication problems. IPC communication is not possible, UDP sockets are not fully featured, DNS lookup is always synchronous and not fully featured. 41 | 42 | There is a lack of proper unit testing of the networking APIs. Certain platforms also miss full implementations of various parts of the networking API. (See https://github.com/HaxeFoundation/haxe/issues/6933, https://github.com/HaxeFoundation/haxe/issues/6816) 43 | 44 | ### Threads, processes, and timers 45 | 46 | Some Haxe targets (e.g. eval) have problematic implementations of threads which can result in unexpected deadlocks or crashes. It is not possible to pass handles (sockets or open files) to open processes (IPC); there is no standardised message passing for child processes. 47 | 48 | ## Detailed design 49 | 50 | The APIs will be implemented as direct wrappers of [libuv](http://libuv.org/) (which is the foundation of Node.js APIs) on targets which allow this, i.e. eval, Neko, HashLink, hxcpp, and Lua. The hxnodejs library will be updated to map Node.js APIs to the new `sys` APIs. 51 | 52 | Java, C#, PHP, and Python may at first expose the new `sys` APIs by requiring a native library (`dll`, `so`, `dylib`). Proper target-native APIs can be added over time, particularly after an in-depth test suite is available. 53 | 54 | The full implementation status is available in the [haxe-sys](https://github.com/Aurel300/haxe-sys) repository. 55 | 56 | ### Errors 57 | 58 | A `haxe.Error` class is added to unify error reporting in the system APIs. It has a `message` field which contains the human-readable description of the error. It also includes a `type` field which can be `switch`-ed on. 59 | 60 | ```haxe 61 | try { 62 | sys.FileSystem.someOperation(); 63 | } catch (err:haxe.Error) { 64 | trace("error!", err); 65 | } 66 | // or 67 | try { 68 | sys.FileSystem.someOperation(); 69 | } catch (err:haxe.Error) { 70 | switch (err.type) { 71 | case FileNotFound: // it's fine 72 | case _: throw err; 73 | } 74 | } 75 | ``` 76 | 77 | > **Unresolved question:** 78 | > 79 | > There are multiple ways of expressing proper type-safe errors for the filesystem API: 80 | > - errors represented by a single `enum` (`sys.FileSystemError`), with the individual cases containing all the information of that particular error 81 | > - awkward to catch individual errors (any `catch` would need a `switch`) 82 | > - fewer classes to maintain, less work to throw errors (the case names the error, so no message is needed) 83 | > - errors represented by sub-classes of a single base class 84 | > - possible to catch individual subclasses in separate `catch` blocks 85 | > - many classes in the package (could be moved into a sub-package for errors?) 86 | > - base class `Error` + enum for types, as implemented in the draft now 87 | > 88 | > The primary aim for any solution is to be able to catch specific types of errors without having to rely on string comparison. 89 | 90 | ### Signals 91 | 92 | A type-safe system for emitting signals (similar to events) is added, similar to `tink_core`. A `Signal` is simply an abstract over an array of listeners (`Listener`). A signal-emitting object has a number of `final` signal instances. 93 | 94 | ```haxe 95 | class Example { 96 | public final fooSignal = new Signal(); 97 | public final barSignal = new Signal(); 98 | public function new() {} 99 | public function emit() { 100 | fooSignal.emit(new NoData()); 101 | barSignal.emit("hello"); 102 | } 103 | } 104 | 105 | class Main { 106 | static function main():Void { 107 | var example = new Example(); 108 | example.fooSignal.on(() -> trace("signal foo")); 109 | example.barSignal.on(str -> trace("signal bar", str)); 110 | example.emit(); 111 | } 112 | } 113 | ``` 114 | 115 | Currently no efforts were made to "hide" the `emit` method (like the `Signal` and `SignalTrigger` distinction made in `tink_core`). 116 | 117 | ### Callbacks 118 | 119 | Asynchronous methods are identical to their synchronous counter-parts, except: 120 | 121 | - their return type is `Void` 122 | - they have an additional, required `callback` argument of type `Callback` or `Callback` 123 | - first argument passed to the callback is a `haxe.Error`, or `null` if no error occurred 124 | - any additional arguments represent the data returned by the call, analogous to the return type of the synchronous method; if the synchronous method has a `Void` return type, the callback takes no additional arguments 125 | - `Callback` is an abstract which has some `from` methods, allowing a callback to be created from functions with a simpler signature (e.g. a `Callback` from `(err:Error)->Void`) 126 | 127 | ### Flags, modes, constants 128 | 129 | Several methods in the API accept constants or a combination of flags. Constants (where the argument is *exactly one of* a set of options) have been converted to an `enum` or `enum abstract`. Flags (where the argument is *zero or more of* a set of options) have been converted to an `abstract` over `Int`, with an overloaded `|` operator. 130 | 131 | ### Streams 132 | 133 | At the core of a lot of Node.js APIs lie [streams](https://nodejs.org/api/stream.html), which are abstractions for data consumers (`Writable`), data producers (`Readable`), or a mix of both (`Duplex` or `Transform`). Streams enable better composition of data operations with methods such as `pipeline`. There is also a mechanism to minimise buffering of data in memory (`highWaterMark`, `drain`) when combining streams. 134 | 135 | ### File descriptors 136 | 137 | The libuv API has a concept of file descriptors, represented by a single integer. To avoid issues with platforms without explicit file descriptor numbers, `sys.io.File` is an `abstract`, similar to the new threading API. 138 | 139 | Various methods which take a file descriptor as their first argument are moved into their own methods in the `File` abstract. 140 | 141 | ### Synchronous / asynchronous versions 142 | 143 | To avoid the `someMethod` + `someMethodSync` naming scheme present in Node.js, the two versions are more clearly split: 144 | 145 | - `asys.FileSystem` and `asys.AsyncFileSystem` (static methods) 146 | - `asys.io.File` and `asys.io.AsyncFile` (instance methods) 147 | 148 | `asys.io.File` exposes an `async` field to access the `asys.io.AsyncFile` corresponding to a particular file. 149 | 150 | ```haxe 151 | // synchronously: 152 | var file = asys.FileSystem.open("file.txt", Read); 153 | var data = file.readFile(); 154 | 155 | // asynchronously: 156 | asys.AsyncFileSystem.open("file.txt", Read, (err, file) -> { 157 | file.async.readFile((err, data) -> { 158 | // ... 159 | }); 160 | }); 161 | ``` 162 | 163 | ### Non-Unicode filepaths 164 | 165 | In libuv, wherever a path is expected as an argument, a `char *` can be provided, equivalent to `haxe.io.Bytes`. Similarly, whenever paths are to be returned, either a `char *` is returned. 166 | 167 | It would be awkward to require `Bytes` objects as file paths in Haxe, so instead, the assumption is made that filepaths will be valid Unicode most of the time, and `haxe.io.FilePath` (an `abstract` over `String`) is used consistently in the new API. In the rare cases that non-Unicode paths are returned, they are escaped into a Unicode string. The original `Bytes` can be obtained with `FilePath.decode(path)`. There is also the inverse `FilePath.encode(bytes)`. 168 | 169 | See https://github.com/HaxeFoundation/haxe/issues/8134 170 | 171 | ### Backward compatibility 172 | 173 | The new APIs reserved for system targets will be available in a new top-level package `asys`. Some cross-platform types will be added to the `haxe` package. A `sys-compat` library will be provided to map the old `sys` APIs to the new `asys` package for easier transitioning and testing, although the old `sys` APIs will remain untouched when the library is not used. 174 | 175 | ### Testing 176 | 177 | The majority of tests for the current `sys` classes should be adapted and reused. It may be worthwhile to adapt the existing tests to test both implementations (with a forced synchronous operation on `sys.async`) so tests are not duplicated. Additional tests should be written to test async-specific features, such as writing multiple files in parallel. 178 | 179 | For methods that were not present in the original APIs, some tests may be based on the extensive [libuv test suite](https://github.com/libuv/libuv/tree/v1.x/test) or the [Node.js test suite](https://github.com/nodejs/node/tree/master/test/parallel). 180 | 181 | ## Impact on existing code 182 | 183 | Existing code should not be affected, unless it uses an `asys` package. 184 | 185 | ## Drawbacks 186 | 187 | Wrapping libuv allows easily supporting new APIs without several separate implementations. This approach may reduce portability on some of our targets, see [detailed design](#detailed-design). 188 | 189 | ## Alternatives 190 | 191 | There are currently no alternatives in Haxe libraries with a similar feature range. It might be possible on some of Haxe targets to back the new APIs with target-native features, but it would also seriously increase the complexity of this project. 192 | 193 | ## Opening possibilities 194 | 195 | - better haxelib 196 | - libuv available in the OCaml code of the compiler - threading and parallelisation may be possible 197 | 198 | ## Unresolved questions 199 | 200 | - [error reporting style](#errors) 201 | - currently all filesize and file position arguments are `Int`, but this only allows sizes of up to 2 GiB 202 | - use `haxe.Int64`? (dependent on better support on all sys targets, e.g. HashLink) 203 | -------------------------------------------------------------------------------- /proposals/0011-local-var-metadata.md: -------------------------------------------------------------------------------- 1 | # Local variable metadata syntax 2 | 3 | * Proposal: [HXP-0011](0011-local-var-metadata.md) 4 | * Author: [Peter Achberger](https://github.com/antriel) 5 | * Status: [implemented in 4.2.0](https://github.com/HaxeFoundation/haxe/issues/9618) 6 | 7 | ## Introduction 8 | 9 | Haxe allows adding metadata on declarations, fields, and expressions. While we can add metadata to local variable declaration expression, we currently cannot add it to the variable itself. 10 | 11 | ## Motivation 12 | 13 | While we can inspect expression metadata in build macros, we cannot do so in expression macro for expressions of the method, that the macro is called from. 14 | This can be a limitation for expression macros that might want to use the local variables based on some metadata (e.g. dependency injection). 15 | 16 | Currently the only solution, that I know of, is moving the metadata along with variable names up to the method, as field metadata, making it readable by the expression macro. 17 | This is less obvious, more prone to errors/typos due to variable name duplication, and more difficult to manage. 18 | 19 | See also https://github.com/HaxeFoundation/haxe/issues/9468. 20 | 21 | ## Detailed design 22 | 23 | I propose a new syntax for local variables: 24 | 25 | ```haxe 26 | var @:meta foo:Bar; 27 | ``` 28 | 29 | This syntax is similar to already supported method arguments' metadata `function foo(@:meta bar:Bar) {}`. 30 | 31 | `haxe.macro.Type.TVar` returned from `haxe.macro.Context.getLocalTVars()` already has `meta` property that could be filled with this new syntax. Currently it's always empty, because normal syntax of `@:meta var foo:Bar;` adds the metadata on the expression, not the variable declaration. 32 | 33 | This syntax shall work regardless of whether the variable has an explicit type, and/or initialization, and shall work with the comma syntax. All those shall therefore be valid: 34 | 35 | ```haxe 36 | var @:meta foo; 37 | var @:meta foo = 'bar'; 38 | var @:meta foo:String; 39 | var @:meta foo, bar:Bool, @:meta c:Int = 0; 40 | ``` 41 | 42 | ## Impact on existing code 43 | 44 | It's a new syntax, so should be none. 45 | 46 | ## Drawbacks 47 | 48 | The only issue is that the new syntax isn't exactly obvious and goes against the usual one that works for member fields (but it is similar to the method arguments' metadata syntax). 49 | 50 | That means e.g. copying member variables into local variables would require more manual changes to keep the same functionality (assuming the expression macro uses both fields and local variables). 51 | 52 | ## Alternatives 53 | 54 | We could also make the current syntax `@:meta var foo:Bar;` duplicate the metadata from the expression to the `TVar`. That would allow us to keep the same syntax, but it might have consequences/issues that I don't see. 55 | 56 | ## Unresolved questions 57 | 58 | None. 59 | -------------------------------------------------------------------------------- /proposals/0012-abstract-classes.md: -------------------------------------------------------------------------------- 1 | # Abstract classes 2 | 3 | * Proposal: [HXP-0012](0012-abstract-classes.md) 4 | * Author: [Aleksandr Kuzmenko](https://github.com/RealyUniqueName) 5 | * Status: [implemented in 4.2.0](https://github.com/HaxeFoundation/haxe/pull/9716) 6 | 7 | ## Introduction 8 | 9 | Introduce classes that provide incomplete implementation and cannot be instantiated directly. 10 | To instantiate an abstract class one has to extend it with supplementing implementation first. 11 | 12 | ## Motivation 13 | 14 | Requests for this feature keep coming up from time to time. 15 | And while Haxe doesn't have such a feature even standard library uses this concept. 16 | 17 | E.g. here is the doc for `haxe.io.Input`: 18 | > An Input is an abstract reader. See other classes in the `haxe.io` package for several possible implementations. 19 | 20 | It could lead to inconsistencies like some abstract methods throw "not implemented" exceptions: 21 | ```haxe 22 | /** 23 | Read and return one byte. 24 | **/ 25 | public function readByte():Int { 26 | #if cpp 27 | throw "Not implemented"; 28 | #else 29 | return throw "Not implemented"; 30 | #end 31 | } 32 | ``` 33 | while others just do nothing: 34 | ```haxe 35 | /** 36 | Close the input source. 37 | 38 | Behaviour while reading after calling this method is unspecified. 39 | **/ 40 | public function close():Void {} 41 | ``` 42 | And it's not obvious if an empty `close` method is a part of a valid implementation or if it's an abstract method, which must be overridden. 43 | 44 | As a consequence I can't tell if [Stdout implementation](https://github.com/HaxeFoundation/haxe/blob/b832af9/std/neko/_std/sys/io/Process.hx#L56) 45 | for Neko is missing `close` implementation by mistake. Because `Stdin` implementation in that same file has `close` implemented. 46 | 47 | With an upcoming [asys feature](https://github.com/HaxeFoundation/haxe/pull/8832) standard library is likely to get a 48 | [few](https://github.com/HaxeFoundation/haxe/blob/b832af9/std/haxe/io/Duplex.hx#L12) 49 | [more](https://github.com/HaxeFoundation/haxe/blob/b832af9/std/haxe/io/Readable.hx#L11) 50 | [abstract](https://github.com/HaxeFoundation/haxe/blob/b832af9/std/haxe/io/Writable.hx#L10) 51 | [classes](https://github.com/HaxeFoundation/haxe/blob/b832af9/std/haxe/io/Transform.hx#L26). 52 | 53 | Five of Haxe targets have native abstract classes. Writing correct externs for such classes doesn't seem viable without macros. 54 | 55 | Haxe developers use `throw "Not implemented"` and similar constructs to emulate abstract classes [quite a lot](https://github.com/search?l=&p=2&q=not+implemented+language%3AHaxe&type=Code). That means some of these runtime errors survive to production. 56 | 57 | With a proper abstract classes all of that could be fixed and unified. 58 | 59 | ## Detailed design 60 | 61 | Class becomes an abstract class if it has `abstract` access modifier: 62 | ```haxe 63 | abstract class SomeAbstr { 64 | ``` 65 | It does not matter if a class has `private` modifier before or after `abstract`: 66 | ```haxe 67 | private abstract class SomeAbstr { 68 | abstract private class AnotherAbstr { 69 | ``` 70 | Abstract method is a method that is declared without an implementation and has `abstract` access modifier: 71 | ```haxe 72 | abstract function implementMe():Void; 73 | ``` 74 | Abstract method is not allowed to have an implementation 75 | ```haxe 76 | abstract function implementMe2():Void { // Error: abstract method cannot have an implementation 77 | trace('hello'); 78 | } 79 | ``` 80 | `abstract` access modifier position among other access modifiers of a method does not matter: 81 | ```haxe 82 | abstract public function implementMe1():Void; //ok 83 | public abstract function implementMe2():Void; //also ok 84 | ``` 85 | Static methods cannot be abstract: 86 | ```haxe 87 | abstract class SomeAbstr { 88 | static abstract function implementMe():Void; // Error: static functions cannot be abstract 89 | } 90 | ``` 91 | Abstract classes may contain static fields just like normal classes: 92 | ```haxe 93 | abstract class SomeAbstr { 94 | static public var field:String = 'hello'; 95 | static public function method():Void {} 96 | } 97 | ``` 98 | Abstract classes may or may not contain abstract methods. 99 | ```haxe 100 | abstract class SomeAbstr { 101 | abstract function implementMe():Void; 102 | } 103 | abstract class AnotherAbstr { } // also ok 104 | ``` 105 | Abstract classes cannot be instantiated: 106 | ```haxe 107 | abstract class Main { 108 | static function main() { 109 | var m = new Main(); // Error: cannot instantiate abstract class Main 110 | } 111 | 112 | public function new() {} 113 | } 114 | ``` 115 | Abstract classes may contain an implementation, but still cannot be instantiated: 116 | ```haxe 117 | abstract class Greeter { 118 | public final name:String; 119 | public function new(name:String) { 120 | this.name = name; 121 | } 122 | public function greet() trace('Hello, $name!'); 123 | } 124 | 125 | class Main { 126 | static public function main() { 127 | new Greeter('world'); //Error: cannot instantiate abstract class Greeter 128 | } 129 | } 130 | ``` 131 | If a class contains abstract methods, then the class itself must be declared `abstract`. 132 | ```haxe 133 | class Some { 134 | abstract function implementMe():Void; // Error: non-abstract class Some cannot contain abstract functions 135 | } 136 | abstract class Some { 137 | abstract function implementMe():Void; // ok 138 | } 139 | ``` 140 | If a class extends an abstract class, but does not implement all abstract methods, then the class itself must be declared abstract: 141 | ```haxe 142 | abstract class Abstr { 143 | abstract function implementMe():Void; 144 | } 145 | class Concrete extends Abstr {} // Error : Concrete is missing an implementation for function implementMe 146 | abstract class AnotherAbstr extends Abstr {} // ok 147 | ``` 148 | Abstract class is not required to implement all the methods of interfaces that class implements. 149 | Instead missing interface implementations are implicitly treated as abstract methods: 150 | ```haxe 151 | interface IFace { 152 | function implementMe():Void; 153 | } 154 | abstract class Abstr implements IFace {} // ok 155 | class Concrete extends Abstr {} // Error : Concrete is missing an implementation for function implementMe 156 | ``` 157 | `abstract` access modifier cannot be used with `final` or `inline` access modifiers: 158 | ```haxe 159 | final abstract class Abstr { // Error: abstract class cannot be final 160 | final abstract function implementMe():Void; // Error: abstract function cannot be final 161 | inline abstract function doSomething():Void {} // Error: inline function cannot be abstract 162 | inline abstract function doSomething():Void; // Error: abstract function cannot be inlined 163 | } 164 | ``` 165 | It's not allowed to inline abstract functions at call site: 166 | ```haxe 167 | abstract class Abstr { 168 | abstract function implementMe():Void; 169 | } 170 | //<...> 171 | var a:Abstr = getAbstr(); 172 | inline a.implementMe(); // Error: abstract function cannot be inlined 173 | ``` 174 | Interfaces and their fields cannot have `abstract` access modifier: 175 | ```haxe 176 | abstract interface IFace { // Error: interfaces don't need abstract modifier 177 | abstract function implementMe():Void; // Error: interface fields don't need abstract modifier 178 | } 179 | ``` 180 | If a class provides an implementation for an abstract method it does not need `override` keyword as no actual implementation is overridden. 181 | ```haxe 182 | abstract class Abstr { 183 | abstract function implementMe():Void; 184 | } 185 | class Concrete extends Abstr { 186 | function implementMe():Void { //no "override" 187 | trace('implemented'); 188 | } 189 | } 190 | ``` 191 | Abstract classes may define abstract getters and/or setters: 192 | ```haxe 193 | abstract class Abstr { 194 | public var prop1(get,set):Int; 195 | function get_prop1() return Std.random(10); 196 | abstract function set_prop1(value:Int):Int; 197 | 198 | public var prop2(get,set):Int; 199 | abstract function get_prop2():Int; 200 | abstract function set_prop2(value:Int):Int; 201 | } 202 | ``` 203 | Abstract classes don't imply any special rules for constructors. 204 | 205 | ## AST changes 206 | 207 | 1. Add `isAbstract` flag to type definitions: 208 | * As `?isAbstract:Null` argument to `TDClass` constructor of [`haxe.macro.TypeDefKind`](https://github.com/HaxeFoundation/haxe/blob/518d019/std/haxe/macro/Expr.hx#L957) (breaking change for macros) 209 | * OR as `var ?isAbstract:Null` field to [`haxe.macro.TypeDefinition`](https://github.com/HaxeFoundation/haxe/blob/518d019/std/haxe/macro/Expr.hx#L892) since it already has optional `isExtern`. 210 | 2. Add `AAbstract` constructor to [`haxe.macro.Access` enum](https://github.com/HaxeFoundation/haxe/blob/518d019/std/haxe/macro/Expr.hx#L813) 211 | 3. Add `var isAbstract:Bool` to [`haxe.macro.BaseType`](https://github.com/HaxeFoundation/haxe/blob/518d019/std/haxe/macro/Type.hx#L351) 212 | 4. Add `var isAbstract:Bool` to [`haxe.macro.ClassField`](https://github.com/HaxeFoundation/haxe/blob/518d019/std/haxe/macro/Type.hx#L188) 213 | 5. Make corresponding changes on the ocaml side. 214 | 215 | ## Impact on existing code 216 | 217 | Proper implementation as described in [AST changes](#AST-changes) would introduce breaking changes for macro development. 218 | Although, proposed changes seem to be minor and should not affect most of Haxe projects. 219 | 220 | Alternatively we can start with a meta `@:abstract` instead of a proper access modifier. 221 | Or parse an access modifier into a meta to avoid any breaking changes for macros while still having a proper access modifier in Haxe code. 222 | 223 | ## Drawbacks 224 | 225 | Using `abstract` keyword for this feature may be confusing especially in a documentation since this keyword is already used for [Abstract types](https://haxe.org/manual/types-abstract.html) 226 | 227 | But using any other keyword would be even more confusing since "abstract classes" is a common term for almost any OOP programming language. 228 | 229 | This probably can be solved to some extent by _always_ using "abstract" accompanied with the correct word (either "abstract type" or "abstract class") 230 | in docs or speeches to form a strong distinction between these two features in the "community mind". 231 | 232 | ## Alternatives 233 | 234 | It is possible to implement abstract classes with macros ([most known implementation by @AndyLi](https://gist.github.com/andyli/5011520)). 235 | 236 | But a macro implementation has obvious disadvantages: 237 | * Could not be used while developing other macros 238 | * Affects compilation time 239 | * Unwanted in the standard library 240 | * Not supported by the compiler to generate target-native abstract classes for better interoperability with the target platform. -------------------------------------------------------------------------------- /proposals/0013-default-type-parameters.md: -------------------------------------------------------------------------------- 1 | # Default type parameters 2 | 3 | * Proposal: [HXP-0013](0013-default-type-parameters.md) 4 | * Author: [Ben Merckx](https://github.com/benmerckx) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10483) 6 | 7 | ## Introduction 8 | 9 | Optionally declare a default type for generic type parameters. 10 | 11 | ````Haxe 12 | class Test {} 13 | $type((null: Test)); // Test 14 | ```` 15 | 16 | ## Motivation 17 | 18 | ### Use case: types 19 | 20 | Some types can benefit from having default type parameters. As an example look at 21 | a generic virtual dom component: 22 | ````haxe 23 | class Component {} 24 | ```` 25 | Users of the library subclass the Component. Often there's no state or props used, 26 | but with current haxe it's required to write out the empty parameters explicitly: 27 | ````haxe 28 | class MyComponent extends Component<{}, {}> {} 29 | ```` 30 | With defaults it's possible to simply extend and leave the defaults to the library: 31 | ````haxe 32 | class Component {} 33 | class MyComponent extends Component {} 34 | class MyComponentWithProps extends Component {} 35 | class MyComponentWithPropsAndState extends Component {} 36 | ```` 37 | 38 | Another use case is simplifying user facing APIs where some types are only necessary 39 | to be given explicitly in very specific cases. The types are ready to be used without 40 | the user having all of the implementation details. 41 | 42 | For example tink_core defines an Error as 43 | ````haxe 44 | typedef Error = TypedError; 45 | ```` 46 | where with default type parameters there would not necessarily have been a distinction 47 | as `Error` and `TypedError` could have been defined as: 48 | ````haxe 49 | class Error {} 50 | ```` 51 | 52 | Another example would be a generic Promise implementation which holds error data as well. 53 | In most cases it makes a lot of sense to default the error type parameter to an error type 54 | that works for the user but would not restrict them from using it differently if the use 55 | case came up. 56 | ````haxe 57 | class Promise {} 58 | ```` 59 | 60 | Using the dom in the javascript target can also demonstrate the use as accessing elements 61 | is usually done through `js.html.Element`. That works as long as you want access to those 62 | properties. But in a few cases you need access to specific properties of the element and 63 | thus want it typed. Say in a lifecycle method of typical virtual dom components 64 | (ignoring state or props here): 65 | 66 | ````haxe 67 | class Component { 68 | onmount(element: E) {} 69 | } 70 | ```` 71 | If you'd like to set the `src` property of an image this can be used as: 72 | ````haxe 73 | class Image extends Component 74 | ```` 75 | But in most other cases you can use `Component` directly without passing a specific element type. 76 | 77 | See also: https://github.com/HaxeFlixel/flixel/issues/1677 78 | 79 | ### Use case: methods 80 | 81 | Methods with a generic type parameter are not always able to infer that type from the parameters (especially if that type is optional). 82 | 83 | ````haxe 84 | function createMyClass(?input: T): MyClass 85 | return new MyClass(if (input == null) new MyDefault() else input); 86 | 87 | $type(createMyClass()); // MyClass 88 | ```` 89 | 90 | Outlined in more detail [here](https://github.com/HaxeFoundation/haxe-evolution/pull/50#issuecomment-413976704) 91 | 92 | ## Detailed design 93 | 94 | - Parse the new type parameter syntax for type declarations 95 | - Ensure the default unifies with possible type guards 96 | - Disallow a type parameter with a default to be followed by one without 97 | - Use the default parameter when the type is used and there's none declared 98 | - Other generic parameters can be used as long as they were defined before the default 99 | ```` 100 | This should work: class A 101 | This shouldn't: class A 102 | ```` 103 | The reasoning has been discussed in [other places](https://github.com/Microsoft/TypeScript/issues/2175) and works. 104 | 105 | ## Impact on existing code 106 | 107 | If the defaults are available in macro context this can break existing macros 108 | that work with type parameters. Otherwise code that does not use the defaults 109 | should function exactly the same. 110 | 111 | ## Drawbacks 112 | 113 | - [Implicit types](https://github.com/HaxeFoundation/haxe-evolution/pull/50#issuecomment-418016806): It can cause some confusion because it's not easy to tell where a type came from. 114 | 115 | ## Alternatives 116 | 117 | - It's possible to emulate with `@:genericBuild` but there's some downsides: 118 | - Can't use those in macros 119 | - Can't use the type as a type hint (`var a: MyGenericBuild;`) 120 | 121 | - Parameters can be inferred on first usage, but only when constructing a type. 122 | 123 | - Aliases can be used to set defaults: `typedef EmptyComponent = Component<{}, {}>` 124 | It usually makes things more complex than necessary, see also: 125 | https://github.com/massiveinteractive/haxe-react/blob/19156680859ac0e27249762101cb8533b911a141/src/lib/react/ReactComponent.hx#L14. 126 | 127 | ## Unresolved questions 128 | 129 | / 130 | -------------------------------------------------------------------------------- /proposals/0014-self-access-for-abstracts.md: -------------------------------------------------------------------------------- 1 | # Self access for abstracts 2 | 3 | * Proposal: [HXP-0014](0014-self-access-for-abstracts.md) 4 | * Author: [Mark Knol](https://github.com/markknol) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10482) 6 | 7 | ## Introduction 8 | 9 | Add a way to access "self" for abstracts, which is a getter for `(cast this:MyAbstract)`. 10 | 11 | Based on [this discussion](https://github.com/HaxeFoundation/haxe/issues/8162) I'd like to propose an optional `as ` for abstracts, which allows to add/name your own "self": 12 | 13 | ```haxe 14 | abstract MyAbstract(MyType) as selfIdent { 15 | 16 | } 17 | ``` 18 | 19 | ## Motivation 20 | 21 | I find myself copy/pasting the following piece of snippet quite a lot, when dealing with abstracts: 22 | ```haxe 23 | var self(get, never):MyAbstract; 24 | inline function get_self() return (cast this:MyAbstract); 25 | ``` 26 | It would be nice if there was a way to solve this in the language. 27 | 28 | #### Use case 1 29 | One actual example use-case would be this simplified abstract (based on [hx-vector2d](https://github.com/markknol/hx-vector2d/blob/master/src/geom/Vector2d.hx#L89) library). 30 | Test on https://try.haxe.org/#5979b (which obviously has handwritten `self`) 31 | 32 | ```haxe 33 | private typedef Vector2dImpl = { x:Float, y:Float } 34 | 35 | @:forward 36 | abstract Vector2d(Vector2dImpl) as self from Vector2dImpl { 37 | public inline function new(x:Float = 0.0, y:Float = 0.0) { 38 | this = {x: x, y: y}; 39 | } 40 | 41 | public inline function inRange(vector:Vector2d, range:Float):Bool { 42 | // `self` is the current abstract instance, so can use custom operators 43 | return (self - vector).length < range * range; 44 | } 45 | 46 | @:op(A - B) public inline function substract(vector:Vector2d):Vector2d { 47 | return clone().substractAssign(vector); 48 | } 49 | 50 | @:op(A -= B) public inline function substractAssign(by:Vector2d):Vector2d { 51 | this.x -= by.x; 52 | this.y -= by.y; 53 | return this; 54 | } 55 | 56 | public var length(get, never):Float; 57 | private inline function get_length():Float return this.x * this.x + this.y * this.y; 58 | 59 | public inline function clone() return new Vector2d(this.x, this.y); 60 | } 61 | ``` 62 | 63 | #### Use case 2 : enum abstracts 64 | Another useful case would be enum abstracts. Without `as item`, it would switch on `String` which requires you to add a `default` case. 65 | 66 | ```haxe 67 | enum abstract Item(String) as item { 68 | var Foo; 69 | var Bar; 70 | 71 | public function getAlternativeName():String { 72 | return switch item { // pattern matching works on enum values 73 | case Foo: "fooo!!" 74 | case Bar: "barr!!" 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | ## Detailed design 81 | 82 | > `abstract MyAbstract(MyType) as { }` 83 | 84 | This feature is only for abstracts. You can name `` however you want, with exception of existing keywords and it should take field name validation in account. 85 | Also, `as ` is completely optional for abstracts. 86 | 87 | If there is already a field with same name as the ident in the scope there should be a duplication error. 88 | 89 | ```haxe 90 | abstract MyAbstract(MyType) as foo { // line 1: Duplicate abstract field declaration : MyAbstract.foo 91 | public inline function foo():Void; 92 | } 93 | ``` 94 | 95 | At the moment, if you forward a field/function and create a same named field, 96 | there is no duplication/override error. I think we don't have to add error messages for that. 97 | 98 | ## Impact on existing code 99 | 100 | Since it is a new feature and is optional, it doesn't break existing code. 101 | 102 | ## Drawbacks 103 | 104 | This proposal allows you to name your own ident which is nice and flexible (it allows self being to forwarded from another abstract?) but it doesn't encourage a standard way of writing. 105 | This downside is maybe minor, but is one to consider. 106 | 107 | ## Alternatives 108 | 109 | Add a `self` (or a different name) keyword to abstracts which does the same. 110 | This would solve the drawbacks, but could break existing code because the keyword could exist in codebases. 111 | 112 | ## Opening possibilities 113 | 114 | - 115 | 116 | ## Unresolved questions 117 | 118 | - 119 | -------------------------------------------------------------------------------- /proposals/0015-local-static-variables.md: -------------------------------------------------------------------------------- 1 | # Local static variables 2 | 3 | * Proposal: [HXP-0015](0015-local-static-variables.md) 4 | * Author: [YellowAfterlife](https://github.com/yellowafterlife) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10477) 6 | 7 | ## Introduction 8 | 9 | Provide syntax for defining static variables within the scope of a specific field. 10 | 11 | ## Motivation 12 | 13 | Sometimes you want to reuse a static variable within a singular method - 14 | perhaps it is a [data structure](https://github.com/YellowAfterlife/sfgml/blob/b7f32f37126d9ab9197d2248693c5a333019b86b/Array.hx#L242) 15 | that you want to reuse an array/buffer so that you allocate the final returned structure "just right", 16 | or [a native function reference](https://github.com/HaxeFoundation/haxe/blob/c4c2d37f80c136e2259485c1d61b0bd8c38fecfe/std/neko/Lib.hx#L202) 17 | that you resolve once on startup 18 | or even just [depth for a recursive toString() call](https://github.com/HaxeFoundation/haxe/blob/c4c2d37f80c136e2259485c1d61b0bd8c38fecfe/std/cs/_std/Array.hx#L33). 19 | Rest assured, it has to be static. And private. Not to be touched by anything else. 20 | 21 | Currently most developers (and the standard library) take the approach of naming it something like `___`, 22 | but there's a limited amount of convenience in doing so, especially as functions grow longer and going back and forth between variable declarations/use location requires utilizing bookmarks to maintain efficiency. 23 | 24 | ## Detailed design 25 | 26 | Suppose we were to recreate HX-JS $bind function in Haxe code, 27 | ```haxe 28 | public static function bind(o:Dynamic, m:Dynamic) { 29 | if (m == null) return null; 30 | static var closureID = 0; 31 | if (m.__id__ == null) m.__id__ = closureID++; 32 | // ... other code 33 | } 34 | ``` 35 | this would compile as if it were 36 | ```haxe 37 | static var __bind_closureID = 0; 38 | public static function bind(o:Dynamic, m:Dynamic) { 39 | if (m == null) return null; 40 | if (m.__id__ == null) m.__id__ = __bind_closureID++; 41 | // ... other code 42 | } 43 | ``` 44 | 45 | Accessing local variables of containing method in initializer expression should be forbidden, 46 | ```haxe 47 | public static function some(i:Int) { 48 | static var trouble = i; // <- illegal 49 | static var trouble2 = function() return ++i; // <- also illegal 50 | } 51 | ``` 52 | Like with normal statics, either a value or an explicit type would be needed for the static variable. 53 | 54 | Simulating block scoping 55 | ```haxe 56 | public static function some() { 57 | // ... 58 | if (condition) { 59 | static var one = 1; 60 | } 61 | return one; // <- ilegal 62 | } 63 | ``` 64 | would be consistent with local variables and preferred for situations where a variable is only really needed within a specific branch. 65 | 66 | ## Impact on existing code 67 | 68 | Currently `static` keyword is entirely forbidden inside method bodies so there should be no impact. 69 | 70 | ## Alternatives 71 | 72 | [As an old saying goes](https://yal.cc/wp-content/uploads/2019/03/haxe-macros.jpg), 73 | most syntactic omissions can be corrected with a macro, and this is no exception, 74 | but it would be preferred to not have to iterate the expression tree build-time 75 | (as practice shows, this slowly adds up). 76 | 77 | Still, an example of such a macro (implementing `@:static var`) can be found [here](https://github.com/YellowAfterlife/sfhx/blob/master/sf/macro/LocalStatic.hx). 78 | 79 | ## Unresolved questions 80 | 81 | Similarly allowing local `static function` could be handy for platforms where there is a cost to creating/invoking closures. 82 | 83 | -------------------------------------------------------------------------------- /proposals/0016-null-coalescing-operator.md: -------------------------------------------------------------------------------- 1 | # Null coalescing operator 2 | 3 | * Proposal: [HXP-0016](0016-null-coalescing-operator.md) 4 | * Author: [RblSb](https://github.com/RblSb) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10478) 6 | 7 | ## Introduction 8 | 9 | Provide syntax for null coalescing operator, which returns the left operand if it is not null, and otherwise returns the right operand. 10 | 11 | ## Motivation 12 | 13 | This operator exists in many other languages and makes null checks simplier and more readable. I think this would be nice addiction to Haxe syntax and future null-safe traversal operator. 14 | 15 | ## Detailed design 16 | 17 | Example with usage of this operator, when `options` object is read-only: 18 | ```haxe 19 | public static function create(options:Options) { 20 | final bgColor = options.bgColor ?? 0x000000; 21 | final title = options.title ?? "Cool Game"; 22 | setOptions(bgColor, title); 23 | 24 | setSize(options.size ?? {w: 20, h: 10}); 25 | } 26 | ``` 27 | This would be same as: 28 | ```haxe 29 | public static function create(options:Options) { 30 | final bgColor = options.bgColor == null ? 0x000000 : options.bgColor; 31 | final title = options.title == null ? "Cool Game" : options.title; 32 | setOptions(bgColor, title); 33 | 34 | setSize(options.size == null ? {w: 20, h: 10} : options.size); 35 | } 36 | ``` 37 | 38 | Another possible usage with future null-safe traversal operator, if it will be implemented: 39 | ```haxe 40 | return options?.advanced?.id ?? 0; 41 | ``` 42 | 43 | Return type of null coalescing operator will be non-nullable, same as with conditional operator. Incorrect type of right operand should error as before. In case of usage with multiple times operands are executed in left to right order, if no brackets are used: 44 | ```haxe 45 | trace(foo ?? bar ?? 5); 46 | // is ((foo ?? bar) ?? 5) 47 | ``` 48 | 49 | ## Impact on existing code 50 | 51 | Should be no impact. 52 | 53 | ## Alternatives 54 | 55 | `foo ?: bar` syntax for this feature, that is used in some languages instead. But in this case would be harder to detect ternary operator typos. 56 | 57 | ## Opening possibilities 58 | 59 | ## Unresolved questions 60 | -------------------------------------------------------------------------------- /proposals/0017-null-safe-navigation-operator.md: -------------------------------------------------------------------------------- 1 | # Null-safe navigation operator 2 | 3 | * Proposal: [HXP-0017](0017-null-safe-navigation-operator.md) 4 | * Author: [Robert Borghese](https://github.com/RobertBorghese) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10479) 6 | 7 | ## Introduction 8 | 9 | Provide [safe-navigation](https://en.wikipedia.org/wiki/Safe_navigation_operator) (aka. optional chaining) to Haxe. This allows for values that may be `null` to be safely accessed, array-accessed, and called without explicitly checking if the value is `null` ahead of time. 10 | 11 | ```haxe 12 | final iAmNull: Null = null; 13 | final iAmString: Null = "Hello!"; 14 | 15 | iAmNull?.length; // null 16 | iAmString?.length; // 6 17 | 18 | // convoluted method of checking first character 19 | if(iAmNull?.toUpperCase()?.split("")?.[0] != null) { 20 | trace("iAmNull is not null and len > 0"); 21 | } 22 | 23 | final imAlsoNull: Null<(Int) -> Void> = null; 24 | imAlsoNull?.(123); // won't be called 25 | ``` 26 | 27 | ## Motivation 28 | 29 | This feature radically simplifies a common programming pattern with the use of a single operator. Specifically, this pattern is the checking of nullable values prior to accessing or calling; however, this often results in tedious `if` statements: 30 | ```haxe 31 | // calling function on nullable variable 32 | if(myObject != null) { 33 | myObject.runMethod(); 34 | } 35 | 36 | // accessing member in chain of possibly null values 37 | final info = if(myObject != null && myObject.data != null && myObject.data.result != null) { 38 | myObject.data.result.confirm(); 39 | } else { 40 | null; 41 | } 42 | 43 | // alternatively... 44 | final info = myObject != null ? (myObject.data != null ? (myObject.data.result != null ? myObject.data.result.confirm() : null) : null) : null; 45 | ``` 46 | The safe navigation operator drastically improves the size and readability of code in these situations. It turns what could be multiple lines or an over-extended line into a smaller expression. 47 | ```haxe 48 | // calling function on nullable variable 49 | myObject?.runMethod(); 50 | 51 | // accessing member in chain of possibly null values 52 | final info = myObject?.data?.result?.confirm(); 53 | ``` 54 | Moreover, it works well with `@:nullSafety(Strict)`, encouraging syntax that checks the value at the moment it's used, preventing issues stemming from modification after the `if` check. 55 | ```haxe 56 | @:nullSafety(Strict) { 57 | var myArray: Null> = [1, 2, 3]; 58 | if(myArray != null) { 59 | myArray.push(4); // valid 60 | if(Math.random() < 0.5) { 61 | myArray = null; 62 | } 63 | myArray?.pop(); // valid 64 | } 65 | } 66 | ``` 67 | 68 | --- 69 | While the feature itself does not provide new functionality, it provides a **single**, concise syntax for multi-nullable chains (as opposed to `if/else`, `?:`, `?(?:):`, etc). As such, I believe it fits perfectly with Haxe's design philosophy. 70 | 71 | Furthermore, it's a very popular feature. Currently, out of the seven source-to-source targets Haxe supports, nearly half support safe navigation (JavaScript, C#, and PHP). Of course, whether or not a source target supports this syntax ~~probably~~ doesn't matter when it comes to implementation, but it shows how Haxe is falling behind with some languages it should be an enticing alternative for. This especially applies to JavaScript, one of the most popular targets for Haxe. 72 | 73 | In addition, this doesn't even take into account the more "modern" languages Haxe competes with, the large majority of which support this feature as well: Kotlin, Swift, Ruby, Crystal, Scala, TypeScript, CoffeeScript, Groovy, Dart. 74 | 75 | Finally, it should also be noted the feature is popular within the Haxe community. With the rise of [ReallyUniqueName's Safety](https://github.com/RealyUniqueName/Safety) and official incorporation into Haxe with `@:nullSafety`, I think the sooner it's added the better. Especially while things like [null-safe std](https://github.com/HaxeFoundation/haxe/pull/10081) are in the works, and the [null-coalescing operator](https://github.com/HaxeFoundation/haxe-evolution/pull/85) is being considered. 76 | 77 | ## Detailed design 78 | 79 | As demonstrated above, `?.` will act as an alternative to `.` for nullable types. The resulting type will always be `Null`. 80 | 81 | Adding this feature will require modification of the `ExprDef` enum to include the safe alternatives to `EField`, `ECall`, and `EArray` through either additional enum values or fields in the enum values. 82 | 83 | In terms of specific syntax, there should never be space between the two characters of the operator. 84 | ```haxe 85 | final nullString: Null = null; 86 | nullString?.length; // safely returns null (Null) 87 | nullString? .length; // error 88 | nullString ?.length; // ok (based on current . behavior) 89 | ``` 90 | 91 | `?.[]` and `?.()` can also be used for array-access or function calls on nullable values as well. The full `?.` is used to prevent conflict with ternary conditions: `a?(b):c` or `a?[b]:c`. 92 | 93 | There can be white space between the `?.` and `[` or `(`, but like before, the `?.` operator should remain intact. 94 | ```haxe 95 | final nullArray: Null> = null; 96 | final nullFunc: Null<() -> Void> = null; 97 | 98 | nullArray?.[2]; // safely returns null 99 | nullFunc?.(); // safely returns null without calling 100 | 101 | nullArray ?. [2]; // valid 102 | nullArray ? . [2]; // invalid 103 | ``` 104 | 105 | If `@:nullSafety` is enabled, the operator should throw an error (or at least a warning) on all types that are not `Null`. 106 | ```haxe 107 | @:nullSafety(Strict) { 108 | final myString: String = "Test"; 109 | myString?.length; // error: "myString" can never be null 110 | } 111 | ``` 112 | 113 | On the other hand, if `@:nullSafety` is enabled, the safe navigation operator will function on `Null` without explicit checks. 114 | ```haxe 115 | @:nullSafety(Strict) { 116 | final nullArray: Null> = null; 117 | nullArray?.length; // valid 118 | nullArray?.[0]; // valid 119 | } 120 | ``` 121 | 122 | ## Impact on existing code 123 | 124 | As mentioned in Detailed Design, this feature would require modification of AST; therefore, it may break compatibility with some macros using switch against all `ExprDef` cases. 125 | 126 | However, beyond that, adding this feature doesn't invalidate any existing syntax, so there should be no problems. 127 | 128 | ## Drawbacks 129 | 130 | The parsing of the `?.` operator may conflict with float literals (`cond?.1:.2`), so additional logic may need to be incorporated into the parsing of the operator. 131 | 132 | With how prevalent null-checks are, it might be a little tedious for developers who choose to optionally "update" their codebase with the new feature. This would not be required and, if anything, would help those trying to make their project fully compatible with `@:nullSafety`. 133 | 134 | ## Alternatives 135 | 136 | Macros can replicate the "safe access" (`?.`) functionality. This is achieved in [Safety](https://github.com/RealyUniqueName/Safety/). Unfortunately, on top of the fact that it can't be used with other macros and affects compiling performance, macros cannot replicate the feature using the standard `?.` syntax and relies upon non-standard syntax like `!.`. 137 | 138 | In addition, macro-created safe array-access and function calls require even weirder or cluckier syntax that comes no where near the desired cleanliness of the proposed syntax. 139 | 140 | ## Unresolved questions 141 | 142 | The syntax will be the biggest question for this feature. The use of `?.` for access is pretty consistent across almost all languages with the feature (JavaScript, C#, Python ([proposal](https://www.python.org/dev/peps/pep-0505/)), Kotlin, Swift, TypeScript, CoffeeScript, Groovy, Dart). 143 | 144 | However, things begin to diverge when it comes to the safe array-access and function call. JavaScript is the only language that uses `jsFunction?.()` or `jsArray?.[0]`. Since Haxe follows the same syntax, it makes sense to follow its lead, but it is one of the more obscure styles compared to other programming languages. An alternative option would be to provide function-alternatives to all std classes' array-access and function calls (`arr?.get(0)` or `arr?.set(0, 1)`). This would be a similar approach to C# and Kotlin's `myFunction?.Invoke(arg1, arg2)`. 145 | 146 | The format of the changes to `ExprDef` are also yet to be determined. 147 | -------------------------------------------------------------------------------- /proposals/0018-number-separators.md: -------------------------------------------------------------------------------- 1 | # Number separators 2 | 3 | * Proposal: [HXP-0018](0018-number-separators.md) 4 | * Author: [Hipreme|Marcelo Silva Nascimento Mancini](https://github.com/MrcSnm) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10480) 6 | 7 | ## Introduction 8 | 9 | Make the following syntax available: 1_000_000 as an alternative for 1000000 10 | 11 | ## Motivation 12 | 13 | I believe that number separator is achievable with macros, although I'm 14 | pretty sure that it would unnecessarily increase compile time and it is 15 | something pretty simple to implement. 16 | 17 | D and Javascript are successful languages which contain those features, 18 | the make a lot easier to read when working with great numbers, as transforming 19 | from nanoseconds to seconds: 1 / 1000000000. It is fairly impossible to read 20 | that number and don't count the zeros to know which number is that, with separators, 21 | we can make triples: 1_000_000_000, then you will check that exists 3 triples of 0. 22 | 23 | The same thing applies to binary, which can be pretty overwhelming, specially if one 24 | decides to use 32 bit binaries: 25 | 26 | 0b00000000000000000000000000000000 27 | vs 28 | 0b0000_0000_0000_0000_0000_0000_0000_0000_0000 29 | 30 | Although unusual, it is possible to happen, and it will make the code much harder to 31 | read. 32 | 33 | ## Detailed design 34 | 35 | When detecting numbers, just check if there is a separator, if it is, just continue 36 | the process of number parsing. I believe the only corner case is from uses trying 37 | to do bad things: 1000_ : Using the separator as the last string to parse could 38 | just send an error. 39 | 40 | ## Impact on existing code 41 | 42 | None. 43 | 44 | ## Drawbacks 45 | 46 | None. 47 | 48 | ## Alternatives 49 | 50 | Macros would make a redundant string parsing, plus having a function to call for 51 | making that syntax available would break the visibility, which is the main reason 52 | for that proposal. 53 | 54 | 55 | ## Unresolved questions 56 | 57 | None. 58 | -------------------------------------------------------------------------------- /proposals/0019-numeric-iteral-suffixes.md: -------------------------------------------------------------------------------- 1 | # Numeric literal suffixes 2 | 3 | * Proposal: [HXP-0019](0019-numeric-literal-suffixes.md) 4 | * Author: [Aidan Lee](https://github.com/aidan63) 5 | * Status: [to be implemented](https://github.com/HaxeFoundation/haxe/issues/10481) 6 | 7 | ## Introduction 8 | 9 | By appending an `i64` to the end of an integer literal (e.g. `final myInt64 = 1000i64;`) it will become a `haxe.Int64` instead of a standard `Int`. 10 | 11 | ## Motivation 12 | 13 | Creating `haxe.Int64` objects is not as easy as standard integers. You either have to use `haxe.Int64.make` and manually split the number into low and high bits if your initial value is too large for a 32bit integer, or you can use the `@:from` function for getting one from an existing `Int`. 14 | 15 | ## Detailed design 16 | 17 | The compiler could detect if a `Const(Int(s))` ends with an `i64` suffix and if so create the necessary high and low bits from `s` and insert a `haxe.Int64.make` call in place. This doesn't require any changes to the AST, only that the lexer allows `i64` suffixes for constant integers. To simplify things the `i` in the suffix is case sensitive, that is `1000I64` is not valid. 18 | 19 | I've put together a quick proof of concept in this branch (Uses previously proposed `L` suffix). https://github.com/aidan63/haxe/tree/int64-suffix 20 | 21 | ## Impact on existing code 22 | 23 | Macro functions which attempts to parse `EConst(CInt(s))` strings might start to fail if they encounter suffixes as this is a new concept to haxe. 24 | 25 | ## Drawbacks 26 | 27 | There are several oddities when using `haxe.Int64` (such as keys for `haxe.ds.Map`, analyser-optimise not simplifying maths) which hasn't effected too many users so far (assumably because `haxe.Int64` doesn't get much use due to it being awkward to use). We might want to make it more consistent across targets before "promoting" it by making it as easy to create as a standard `Int` type. 28 | 29 | ## Alternatives 30 | 31 | This could be done currently as a macro function but having to copy around a function into each project or pull in an external library to make creating a core numeric type easier isn't the best experience. 32 | 33 | ## Opening possibilities 34 | 35 | There are a couple of other numeric types (`Single`, `UInt`) which are also under used and have inconsistent behaviour across targets, they could also gain suffixes in the future. Not sure if it's still planned but I think I remember hearing on one of the haxe livestreams that there were plans for more sized integer types, if this is the case more suffixes could be added to support those types as well. 36 | 37 | Below is a table of possible suffix for intergers and floats and the potential types they would match to. 38 | 39 | |suffix|signed integer size| 40 | |--|--| 41 | |i8|1 byte| 42 | |i16|2 bytes| 43 | |i32|4 bytes| 44 | |i64|8 bytes| 45 | 46 | |suffix|unsigned integer size| 47 | |--|--| 48 | |u8|1 byte| 49 | |u16|2 bytes| 50 | |u32|4 bytes| 51 | |u64|8 bytes| 52 | 53 | |suffix|float size| 54 | |--|--| 55 | |f32|4 bytes| 56 | |f64|8 bytes| 57 | 58 | ## Unresolved questions 59 | 60 | - Currently the compiler will error if you type a hexadecimal number with more than 8 characters (excluding `0x`), if we want hex literals to support the suffix should that be increased to 16 if suffixed? 61 | --------------------------------------------------------------------------------