E11070: nested call in throw - extract intermediate variable for the exception

Category: E11xxx Code quality and IR analysis · Reported in phase: PRE_IR_CHECKS · Classification: NESTED_CALL_IN_THROW · All error codes

What went wrong

A throw statement was given a nested call expression of the form `throw factoryFn(arg)(otherArg)`. EK9 only allows the throw statement to take either a constructor call or a previously-declared variable. Higher-order factory chains that produce an Exception must be materialised into a named variable first, then thrown.

Worked examples

Compilable examples related to this error:

  • ek9 -q 1303 (Why does EK9 reject a call nested inside a throw expression?)

Rationale

`throw` is a control-flow boundary. The exception object that flows across that boundary is the most important value in the surrounding code path — it determines which catch will run, what message the operator will see, and what gets logged. Hiding that object inside a nested factory chain conflates two distinct concerns:

  1. SELECTING / CONSTRUCTING the exception
  2. THROWING the exception

When these run together on a single line, as in 'throw getExceptionFactory("std")("oops")', a reader cannot tell at a glance:

  • what type of exception is actually thrown
  • which factory was selected and why
  • what arguments were passed to which call
  • whether the inner call has any side effects

Forcing extraction makes every one of those concerns visible - the factory selection, the exception construction and the throw site each on a line of their own:

factory <- getExceptionFactory("std")
ex <- factory("oops")
throw ex

Each line is independently understandable. The exception type is named in its declaration. Debugging can break on the construction or on the throw separately. Logging or enrichment can be inserted between construction and throw without restructuring the code.

This rule pairs with E11069 (excessive call chain): both express the same principle that values flowing through more than one call layer should pass through a named intermediate so the types and intent stay visible.

How to fix it

Extract the exception construction into its own variable, then throw that variable (as above). A direct constructor call in throw position is still allowed and idiomatic, because it expresses construction and throw together without hiding a factory selection step: 'throw Exception("oops")'. The [EXAMPLE] section below shows the nested call in a throw, then both forms that are allowed.

See also

  • E11069: excessive call chain
  • E11013: excessive expression complexity

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E11070: nested call in throw - extract intermediate variable for the exception

#!ek9
defines module error.e11070.bad

  defines function

    ExceptionFactory() as pure abstract
      -> reason as String
      <- builtException as Exception?

    StandardExceptionFactory() is ExceptionFactory as pure
      -> reason as String
      <- builtException as Exception: Exception(reason)

    getExceptionFactory()
      -> factoryKind as String
      <- chosenFactory as ExceptionFactory: StandardExceptionFactory
      if factoryKind == "alt"
        chosenFactory: StandardExceptionFactory

    //Throw of a nested call: the factory selection step is hidden inside the throw
    badThrowNestedCall()
      stdout <- Stdout()
      try
        throw getExceptionFactory("std")("oops")
      catch
        -> caught as Exception
        stdout.println(caught.reason())

Fixed: Build the message first, then throw

#!ek9
defines module error.e11070.good

  defines function

    ExceptionFactory() as pure abstract
      -> reason as String
      <- builtException as Exception?

    StandardExceptionFactory() is ExceptionFactory as pure
      -> reason as String
      <- builtException as Exception: Exception(reason)

    getExceptionFactory()
      -> factoryKind as String
      <- chosenFactory as ExceptionFactory: StandardExceptionFactory
      if factoryKind == "alt"
        chosenFactory: StandardExceptionFactory

    //Form 1: direct constructor call - construction and throw on one line, no factory hidden
    throwDirectConstructor()
      stdout <- Stdout()
      try
        throw Exception("direct construction")
      catch
        -> caught as Exception
        stdout.println(caught.reason())

    //Form 2: extract intermediate variable, then throw - factory selection visible
    throwExtractedVariable()
      stdout <- Stdout()
      try
        factory <- getExceptionFactory("std")
        ex <- factory("oops")
        throw ex
      catch
        -> caught as Exception
        stdout.println(caught.reason())

Questions and answers

These Q&A entries show E11070 and its fix: