Class ResourceCloser

java.lang.Object
org.ek9lang.compiler.phase7.generator.AbstractGenerator
org.ek9lang.compiler.phase7.generator.ResourceCloser

public final class ResourceCloser extends AbstractGenerator
THE compiler-generated close: the one place IR for closing a resource is built, so every route that closes something names the same owner, the same method, the same metadata and the same failure rule.

The failure rule (shape B). Each _close() runs in its own protected region (the JLS 14.20.3.1 nested-try shape), so one close that throws never stops the others. The FIRST EK9 Exception is the one that propagates once every close has run: the body's when the body failed (it is caught into the region's first-failure variable, never replaced), otherwise the first close's. Every later close failure is attached to it as SUPPRESSED (Ek9CloseFailures.suppress). A Panic is not an EK9 Exception: no region here catches it, so a Panic in a close aborts the closes that remain, and a Panic in the body still runs them (they are a finally) and then propagates unchanged - the first failure is only rethrown AFTER the region, on a path a pending Panic never reaches, so a close failure cannot replace a Panic.

The null guard is what makes a partial acquisition safe: a value that was never produced (an earlier call threw) is still null and is skipped (Contract 3).

  • Constructor Details

  • Method Details

    • newFirstFailure

      public ResourceCloser.FirstFailure newFirstFailure()
      A new first-failure variable for one region. Declare it (declareFirstFailure(ResourceCloser.FirstFailure, DebugInfo)) before the region - for try-with-resources before the resource scope, beside the resources' own declarations.
    • newFirstFailure

      public ResourceCloser.FirstFailure newFirstFailure(String ownerScopeId)
      newFirstFailure() for a region made in a scope that is not yet current where the variable is declared (try-with-resources: the resource scope, entered after the declarations).
    • declareFirstFailure

      public IRInstr declareFirstFailure(ResourceCloser.FirstFailure firstFailure, DebugInfo debugInfo)
      SLOT_DECLARE firstFailure, org.ek9.lang::Exception, owner - null until a failure is recorded. Declared before its owner scope may be open (Contract 3); the owner is open where the slot is first filled.
    • closeCall

      public IRInstr closeCall(ResourceCloser.Closing closing)
      CALL variable._close() on the type that declares close.
    • closingRegion

      public List<IRInstr> closingRegion(List<IRInstr> body, List<ResourceCloser.Closing> closings, ResourceCloser.FirstFailure firstFailure, DebugInfo debugInfo)
      THE owned region: body, then - on the normal path, after an EK9 Exception, and while a Panic unwinds - every value in closings that is present is closed, last first, each in its own protected region; then the first EK9 Exception (the body's, else the first close's) is rethrown with the later close failures suppressed onto it.
        SCOPE_ENTER chain_scope
        CONTROL_FLOW_CHAIN TRY_CATCH_FINALLY
          try:     SCOPE_ENTER try_scope, body..., SCOPE_EXIT try_scope
          catch:   -> _t as org.ek9.lang::Exception          (never a Panic)
                   STORE firstFailure, _t
          finally: per closing, last first:
                     load..., IS_NULL / BRANCH_TRUE skip
                     TRY_CATCH_FINALLY  try: CALL value._close()   catch -> _ex: record _ex   (see below)
                     LABEL skip
        SCOPE_EXIT chain_scope
        IS_NULL firstFailure / BRANCH_TRUE done / THROW firstFailure / LABEL done
      
      Recording a close failure: the first one is stored into firstFailure; a later one is CALL_STATIC Ek9CloseFailures.suppress(firstFailure, _ex). The rethrow sits AFTER the region: a pending Panic leaves through the finally's own rethrow and never reaches it.

      The caller declares firstFailure beforehand (declareFirstFailure(ResourceCloser.FirstFailure, DebugInfo)); the region registers it to the scope that is current when this is called. body must not contain the try scope's own enter/exit - the region adds them.

    • closeEach

      public List<IRInstr> closeEach(List<ResourceCloser.Closing> closings, ResourceCloser.FirstFailure firstFailure, DebugInfo debugInfo)
      closingRegion(List, List, ResourceCloser.FirstFailure, DebugInfo) with no body: every value that is present closed, last first, each in its own protected region, then the first close failure rethrown with the later ones suppressed onto it. With nothing to protect there is no outer region (a default operator close).
    • failureRegion

      public List<IRInstr> failureRegion(List<IRInstr> body, List<ResourceCloser.Closing> closings, ResourceCloser.FirstFailure firstFailure, DebugInfo debugInfo)
      The closes of closingRegion(List, List, ResourceCloser.FirstFailure, DebugInfo), run only when body fails with an EK9 Exception - which is then rethrown with any close failure suppressed onto it. A Panic is not caught: it leaves with nothing closed.
        SCOPE_ENTER chain_scope
        CONTROL_FLOW_CHAIN TRY_CATCH_FINALLY
          try:     SCOPE_ENTER try_scope, body..., SCOPE_EXIT try_scope
          catch:   -> _t as org.ek9.lang::Exception
                   STORE firstFailure, _t
        SCOPE_EXIT chain_scope
        IS_NULL firstFailure / BRANCH_TRUE done
        per closing, last first: load..., IS_NULL / BRANCH_TRUE skip, protected CALL value._close(), LABEL skip
        THROW firstFailure
        LABEL done
      
      The caller declares firstFailure beforehand (declareFirstFailure(ResourceCloser.FirstFailure, DebugInfo)).
    • closeRecording

      public List<IRInstr> closeRecording(ResourceCloser.Closing closing, ResourceCloser.FirstFailure firstFailure)
      ONE value, closed if present, in its own protected region that records an EK9 Exception into firstFailure (the first) or suppresses it onto that (any later) - for a caller that holds the values itself and must do more between two closes (the application takes each registered component out of the registry, closes it and drops it: ApplicationClose). The caller ends with rethrow(ResourceCloser.FirstFailure, DebugInfo).
    • rethrow

      public List<IRInstr> rethrow(ResourceCloser.FirstFailure firstFailure, DebugInfo debugInfo)
      After a caller's own sequence of closeRecording(ResourceCloser.Closing, ResourceCloser.FirstFailure): THROW firstFailure when one was recorded.
    • finallyRegion

      public List<IRInstr> finallyRegion(List<IRInstr> body, Supplier<List<IRInstr>> finallyBuilder, DebugInfo debugInfo)
      body, then finallyBuilder's instructions on EVERY exit - its normal end, an EK9 Exception, a Panic, a cancelled async task's Cancelled - with nothing caught: a TRY_CATCH_FINALLY with no catch, in its own chain scope. An async pipeline's cancel and close use it (StreamEmissionEngine), so no task outlives its statement. body must not contain the try scope's own enter/exit - the region adds them.