SomeRanDev / reflaxe.CPP

An alternative C++ target for Haxe that generates dependent-less, GC-less C++17 code.
MIT License
72 stars 5 forks source link

Have constructor list for more human like C++ code as well as more flexible immutable variables (Ignore the failed testcases, trust me bro) #50

Closed Just-Feeshy closed 4 months ago

Just-Feeshy commented 4 months ago

This will most likely fail a good portion of unit tests due to the change in how constructors are compiled now. I added constructor list support for the compiler because I came across a situation with my own learning project that would almost require the argument to be put in the constructor list since the class's property variable was immutable. Let me show an example. I do recommend doing some further tests of your own to see if there might be any issues.

This is the Test.hx I used:

package;

class Test {
        public var lol:String;
        public var hah:String;

        @:const private var test:String;

        public function gyatt() {
            trace("gyatt");
        }

        public function new(hah:String) {
            for (i in 0...hah.length) {
                trace(i);
            }

            this.lol= "lol";
               this.hah = hah;
            trace("Hello World");
            this.start();

            this.test = "test 2";
        }

        public function start() {
            trace("Starting...");
            trace("test: " + test);
        }
}

And here is how it would look before this PR:

#include "Test.h"

#include <iostream>
#include <memory>
#include <string>
#include "_AnonStructs.h"
#include "haxe_Log.h"

using namespace std::string_literals;

Test::Test(std::string hah):
    _order_id(generate_order_id())
{
    int _g = 0;
    int _g1 = (int)(hah.size());

    while(_g < _g1) {
        int i = _g++;

        haxe::Log::trace(i, haxe::shared_anon<haxe::PosInfos>("Test"s, "Test.hx"s, 15, "new"s));
    };

    this->lol = "lol"s;
    this->hah = hah;
    std::cout << "Test.hx:20: Hello World"s << std::endl;
    this->start();
    this->test = "test 2"s;
}

void Test::gyatt() {
    std::cout << "Test.hx:10: gyatt"s << std::endl;
}

void Test::start() {
    std::cout << "Test.hx:27: Starting..."s << std::endl;
    haxe::Log::trace("test: "s + this->test, haxe::shared_anon<haxe::PosInfos>("Test"s, "Test.hx"s, 28, "start"s));
}

And I received an error since I'm trying to overload an immutable variable. However, I really just want to initialize my variable with my argument.

src/Test.cpp:27:13: error: no viable overloaded '='
   27 |         this->test = "test 2"s;
      |         ~~~~~~~~~~ ^ ~~~~~~~~~
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1113:3: note: candidate function not viable: 'this' argument has type 'const std::string' (aka 'const basic_string<char>'), but method is not marked const
 1113 |   operator=(const basic_string& __str);
      |   ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1119:47: note: candidate template ignored: requirement '!__is_same_uncvref<std::string, std::string>::value' was not satisfied [with _Tp = basic_string<char>]
 1119 |   _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const _Tp& __t) {
      |                                               ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1125:69: note: candidate function not viable: 'this' argument has type 'const std::string' (aka 'const basic_string<char>'), but method is not marked const
 1125 |   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(basic_string&& __str)
      |                                                                     ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1131:69: note: candidate function not viable: 'this' argument has type 'const std::string' (aka 'const basic_string<char>'), but method is not marked const
 1131 |   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(initializer_list<value_type> __il) {
      |                                                                     ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1135:69: note: candidate function not viable: 'this' argument has type 'const std::string' (aka 'const basic_string<char>'), but method is not marked const
 1135 |   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const value_type* __s) {
      |                                                                     ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/string:1141:85: note: candidate function not viable: 'this' argument has type 'const std::string' (aka 'const basic_string<char>'), but method is not marked const
 1141 |   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string& operator=(value_type __c);
      |                                                                                     ^
1 error generated.

Now with this PR, I can do exactly that without removing the @:const keyword:

#include "Test.h"

#include <iostream>
#include <memory>
#include <string>
#include "_AnonStructs.h"
#include "haxe_Log.h"

using namespace std::string_literals;

Test::Test(std::string hah):
    _order_id(generate_order_id()), test("test 2"s), hah(hah), lol("lol"s)
{
    int _g = 0;
    int _g1 = (int)(hah.size());

    while(_g < _g1) {
        int i = _g++;

        haxe::Log::trace(i, haxe::shared_anon<haxe::PosInfos>("Test"s, "Test.hx"s, 15, "new"s));
    };
    std::cout << "Test.hx:20: Hello World"s << std::endl;
    this->start();
}

void Test::gyatt() {
    std::cout << "Test.hx:10: gyatt"s << std::endl;
}

void Test::start() {
    std::cout << "Test.hx:27: Starting..."s << std::endl;
    haxe::Log::trace("test: "s + this->test, haxe::shared_anon<haxe::PosInfos>("Test"s, "Test.hx"s, 28, "start"s));
}

There we go! It works!

Test.hx:15: 0
Test.hx:15: 1
Test.hx:15: 2
Test.hx:15: 3
Test.hx:15: 4
Test.hx:15: 5
Test.hx:15: 6
Test.hx:15: 7
Test.hx:15: 8
Test.hx:15: 9
Test.hx:15: 10
Test.hx:20: Hello World
Test.hx:27: Starting...
Test.hx:28: test: test 2
Main.hx:3: Hello World
SomeRanDev commented 4 months ago

OOF, I think I merged this too quickly. When you said tests didn't work, I assume the tests just needed to be regenerated... but this actually breaks a lot of stuff. 😅

Mainly the direct String manipulation breaks lambda assignments in constructors and other large constructions in there. For example, Issue38_CallFindFuncDataOnVar test generates like this:

Main::Main():
    _order_id(generate_order_id()), fn([&]() mutable {  std::cout << "test/unit_testing/tests/Issue38_CallFindFuncDataOnVar/Main.hx:12: do something"s << std::endl)
{

    };
    this->fn();
}

I wrote this as a comment in the code, but I guess seeing how troublesome this is, I think my "TODO" has to happen immediately for this feature to be properly added. The correct way to do this would be to analyze the constructor function body's TypedExpr. This can be done above the following code using bodyExpr:

XComp.pushTrackLines(useCallStack);
body.push(Main.compileClassFuncExpr(bodyExpr));
XComp.popTrackLines();

If bodyExpr is TBlock, extract the expressions from it and read the expressions from the start of the list as long as they are TBinop(OpAssign, TField(e, name), e2) expressions where the e expression is a TConst(TThis). Filter out expressions where e2 is not simple or constant. Haxe always generates the this->NAME = INIT_VALUE expressions at the start of the constructor, so once those stop appearing, you can end the process. You would then need a system to ignore these expressions when generating in the Expressions compiler, not sure exactly how to do that yet. Hmmm...

SomeRanDev commented 4 months ago

Also, don't forget the fields are compiled before functions, so maybe the ones you want to initialize in the constructor can be stored there and then accessed later? There's a lot of ways this could be done, sorry my code is such a mess.

Just-Feeshy commented 4 months ago

I assume that this is still under ctx.isConstructor since we are only working with the constructor's fields.