On Tue, Apr 14, 2026, at 9:27 AM, Rob Landers wrote:
> On Tue, Apr 14, 2026, at 16:18, Rob Landers wrote:
>> 
>> 
>> On Tue, Nov 4, 2025, at 21:13, Larry Garfield wrote:
>>> Arnaud and I would like to present another RFC for consideration: Context 
>>> Managers.
>>> 
>>> https://wiki.php.net/rfc/context-managers
>>> 
>>> You'll probably note that is very similar to the recent proposal from Tim 
>>> and Seifeddine.  Both proposals grew out of casual discussion several 
>>> months ago; I don't believe either team was aware that the other was also 
>>> actively working on such a proposal, so we now have two.  C'est la vie. :-)
>>> 
>>> Naturally, Arnaud and I feel that our approach is the better one.  In 
>>> particular, as Arnaud noted in an earlier reply, __destruct() is unreliable 
>>> if timing matters.  It also does not allow differentiating between a 
>>> success or failure exit condition, which for many use cases is absolutely 
>>> mandatory (as shown in the examples in the context manager RFC).
>>> 
>>> The Context Manager proposal is a near direct port of Python's approach, 
>>> which is generally very well thought-out.  However, there are a few open 
>>> questions as listed in the RFC that we are seeking feedback on.
>>> 
>>> Discuss. :-)
>>> 
>>> -- 
>>>   Larry Garfield
>>>   [email protected]
>>> 
>> 
>> Hi Larry/Arnaud,
>> 
>> This is a pretty exciting thread and fascinating proposal. That being said, 
>> I have a couple of subtle questions that don't seem to be answered in the 
>> (very long) thread or the RFC itself -- If I missed it, please let me know:

>>  1.  What happens if a Fiber is suspended in the using block and never 
>> resumed? When is the using block released to clean up the context?

Since it decomposes to a try-catch-finally, it will exit whenever the finally 
block would have run if you'd just typed out try-catch-finally yourself.  
Arnaud checked, and confirmed that when the fiber is destroyed the using block 
will exit in a success case (ie, exitContext(null)).

>>  2. There's still no mention of how this should affect debugging, will we 
>> see the "desugared" or "sugared" version? Is that even a concern for the RFC?

Error messages would see the original code, so "error on line X" would be based 
on the original `using` block.  That's the same as any other desugaring we 
already do.  (PIpes, PFA, constructor promotion, etc.)  Debuggers will see the 
materialized opcodes, again, the same other desugaring cases.

>>  3. I will say it is weird to have exitContext return an exception; but what 
>> happens if an exception is thrown during exitContext? Why not just have it 
>> return void and throw if you need to throw instead of having two paths to 
>> the same thing?

There's a subtle but important difference here: An exception passed through 
exitContext() is the original exception from lower in the call stack, and its 
backtrace will be the original location of the error.  An exception thrown from 
within exitContext() itself indicates a failure that the Context Manager is 
responsible for, usually an error in the exitContext() logic itself.

Technically a Context Manager can wrap-and-rethrow the exception if it wants, 
but then it is "claiming ownership" over it, just like in any other case of 
wrap-and-rethrow.

Our expectation is that 90% of the time, "let the exception propagate up 
unimpeded" is the desired behavior.  This approach makes "return $e" the right 
thing to do almost-always, which is nice and simple to remember.

See the "return values and exception handling" section for a discussion of this 
in more detail.  As I said in a previous reply, our constraints are different 
than Python's so we end up with a different solution.  If you have a suggestion 
for an alternate approach to the problem, we're happy to listen.

>>  4. Looking at the desugared form ... I'm a bit confused: if exitContext is 
>> called during the finally path and returns an exception, it is just 
>> swallowed? But if it is thrown, it won't be?

The finally path is only reached in case of a successful exit.  Therefore there 
is no exception to pass in, and thus returning an exception is meaningless.  If 
exitContext() throws a new exception of its own (which would indicate an error 
in its own logic), that  will just bubble up past the `using` block entirely, 
which is what we want.

>>  5. That being said, I don't think the RFC shares with us when we should 
>> return an exception vs. throw an exception.

See the "return values and exception handling" section.  If something there 
isn't clear, let me know and I will try to clarify further.

>> — Rob
>
> Maybe the desugared version should look more like this?
>
> } catch (\Throwable $e) {
>     try {
>         $__mgr->exitContext($e);
>     } catch (\Throwable $cleanupException) {
>         throw new ContextManagerException(
>             $cleanupException->getMessage(),
>             previous: $e
>         );
>     }
>     throw $e;
> }
>

I'm not sure I see a reason to force any new exceptions to be only of the 
ContextManagerException type.  If there's a TypeError inside exitContext() or 
something, I'd expect that to be propagated as a TypeError.

--Larry Garfield

Reply via email to