Re: PHP True Async RFC - Stage 2

From: Date: Wed, 19 Mar 2025 15:51:38 +0000
Subject: Re: PHP True Async RFC - Stage 2
References: 1 2 3 4 5 6 7 8 9 10  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
>
>  "Cheating" in the sense that you wrote out a "general syntax",
>
I got it.

> That's not the problem; the problem is that the following are all
equivalent expressions:
>  (((foo())))

In principle, this is not a problem because the expression in parentheses
preceded by spawn is unambiguously a call expression.
That is, from an analysis perspective, everything is fine here because the
expression spawn () is a syntax error.

On the other hand, the expression spawn (()) is also an error, but a
semantic one, because there is nothing to call.

The expression spawn ((), (), ()) is also an error because it attempts to
specify a list of parameters without a call expression.

It seems I didn't make any mistakes anywhere?

>
> But from a user's point of view, I hate rules like that which mean subtle
changes completely change the meaning of the code, in a very specific
context.
>

From the user's perspective, what is the difference between these two
expressions?

```php
spawn function(): string { ... };
spawn (function(): string { ... });
```

>
>  All of these are examples of the keyword being followed by *a function
call*, not *any expression*. Which honestly I think is the right way to go.
>

I created these examples according to the syntax rules from the Bison file
for function_call.
Of course, I might have missed something, but overall, the list above
essentially represents what function_call should expand into.

If we define the rule spawn function_call, then spawn acts as a prefix
in this expression. However, everything that is already defined for
function_call in PHP must remain valid.

If we take the most complex part:
```bison
|   callable_expr { $<num>$ = CG(zend_lineno); } argument_list {
        $$ = zend_ast_create(ZEND_AST_CALL, $1, $3);
        $$->lineno = $<num>2;
    }

callable_expr:
    callable_variable       { $$ = $1; }
  | '(' expr ')'           { $$ = $2; }
  | dereferenceable_scalar { $$ = $1; }
  | new_dereferenceable    { $$ = $1; }
;
```

we can see that the expression (((some()))) corresponds to the second line,
and arguments must follow.
However, in this case, arguments become *mandatory*. This means that after
(((some()))), there must also be ().

And another good thing about this syntax: a coroutine is an execution
context, and a function call is a transition to a block of code.
This means that the expression spawn function_call can be understood
literally as:

* Create a new execution context
* Execute function_call

The syntax for function_call is already defined in PHP, and nothing
changes in it.

The second form of the spawn expression is:
```php
spawn inline_function
```

where inline_function is a definition from bison:

```bison
inline_function:
function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars
return_type
...;
```

Another advantage of this solution is that if function_call constructs
change in the future, we won’t need to modify the definitions for spawn.

>
>  If it's going to be a special case for an "inline coroutine", just use a
keyword other than "function", so it doesn't look like an expression when
it's not, like "spawn block { ... }"; or no keyword at all, just "spawn {
... }"
>

Well, yes, but here we again face the issue with returnType syntax, which
ends up hanging in the air...


Thread (59 messages)

« previous php.internals (#126846) next »