hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.46k stars 238 forks source link

[BUG] `cppfront` confused by function expression in `for`'s (`next`) expression #432

Closed JohelEGP closed 9 months ago

JohelEGP commented 1 year ago

Title: Confused by function expression in for's (next) expression.

Description:

It seems something enters a bad state. Unparenthesized, it complains about a missing do. Parenthesized, lifetime analysis says the loop variable is uninitialized.

Minimal reproducer (https://cpp2.godbolt.org/z/Er81zc91E, https://cpp2.godbolt.org/z/a75sW77bs):

main: (args) = {
  for args next :() = 0 do (arg) std::cout << arg;
}
main: (args) = {
  for args next (:() = 0) do (arg) std::cout << arg;
}

Commands:

cppfront -clean-cpp1 main.cpp2

Actual result and error:

main.cpp2(2,25): error: 'for range' must be followed by 'do ( parameter )' (at 'do')
main.cpp2(2,49): error: local variable arg is used before it was initialized
  ==> program violates initialization safety guarantee - see previous errors
filipsajdak commented 1 year ago

:() = 0 unnamed function should end with ; (semicolon). There is a bug that I have introduced by adding a check for double semicolons after short function syntax. Probably can be fixed by the following patch:

diff --git a/source/parse.h b/source/parse.h
index f716925..3165ec0 100644
--- a/source/parse.h
+++ b/source/parse.h
@@ -3606,6 +3606,7 @@ private:
                     && curr().type() != lexeme::LeftParen               // not imediatelly called
                     && curr().type() != lexeme::RightParen              // not as a last argument to function
                     && curr().type() != lexeme::Comma                   // not as first or in-the-middle, function argument
+                    && curr().type() != lexeme::Keyword                 // not as next expression
                 ) {
                     // this is a fix for a short function syntax that should have double semicolon used
                     // (check comment in expression_statement(bool semicolon_required))

Additionally, arg is not treated as initialized... don't know why.

Also, shall we allow statements as for-body? I was sure that it require a compound statement as ifs.

JohelEGP commented 1 year ago

:() = 0 unnamed function should end with ; (semicolon).

When? Always? When not an expression-statement (the only case that would require double ;s, AFAIK)?

filipsajdak commented 1 year ago

Ha! You are right, and I am mistaken. I am pretty sure it was required before. Maybe my memory failed here.

The following code explains it (https://cpp2.godbolt.org/z/o11cYnKj6):

main: () -> int = {

    l := :() = std::cout << 42;                         // require one semicolon
    l();

    :() = std::cout << " >>instantly called<< ";();     // require one semicolon before ()

    return fun(:() -> int = 0+1, :() = std::cout << 1); // semicolon is not required in both cases

}

fun: (f, g) -> int = {
    g();
    return f();
}
JohelEGP commented 1 year ago

By the way, I did try adding ;s before reporting. The results were the same, so I chose to not mention that.

I was just wondering if you had the answer about ; after a simple function expression.

I've thought about it some more, and I think the answer lies in this grammar:

//G expression-statement:
//G     expression ';'
//G     expression

So the ; is optional when parsing the function expression's statement. Except when the FE is a statement itself (rejected during parse, probably left-shifted from semantic analysis):

main: () = {
  :() = 0 // `f` can't be part of the FE's statement, but still requires `;`.
  f();
}

When the FE is in an expression-list, ,, ), and ], can end the it (; or not). As they would any other primary-expression (the , thanks to no operator,).

See https://cpp2.godbolt.org/z/6j9rq1bvb.

realgdman commented 1 year ago

I've been exploring missing ; in short-body lambdas and tried to invent dangerously looking example, and want to share my attempt so far

foo: (x: int) -> int = { return x; }
a : std::function<int(int)> = foo&; //don't know another ways to express ptr to fn

main: () = {
    s1 := :(x) ->_ =a;(1);
    s2 := :(x) ->_ =a(2);   //note missing ;

    std::cout << s1(3) << std::endl; //prints 3
    std::cout << s2(4) << std::endl; //prints 2

    supress_last_use(s1, s2);
}

supress_last_use: (x,y) = {}