E11069: nested call chain exceeds one level - extract intermediate variable for readability

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

What went wrong

A nested call chain deeper than one level was detected. EK9 supports single-level functional chaining (e.g., getTransformer("upper")("text")) but rejects deeper nesting as it forces inside-out mental evaluation that obscures the types and intent of the code.

Worked examples

Compilable examples related to this error:

  • ek9 -q 1342 (Why does EK9 reject a deeply nested higher-order call...)

Rationale

Single-level chaining is idiomatic in functional programming: in 'getProcessor("upper")("hello")' the reader sees getProcessor returns a delegate, and that delegate accepts "hello". The types are clear from context.

Deeper chains force reverse-order mental evaluation. To understand 'getMaker("simple")("seed")("text")' (the [EXAMPLE] section below), you must:

  1. Evaluate getMaker("simple") - what type does it return?
  2. Call that result with ("seed") - what type does THAT return?
  3. Call that result with ("text") - finally get the answer.

This is the same cognitive problem as C's nested pointer dereferences: (*(**fp)(x))(y). Each level multiplies the mental effort required to trace types through the chain.

Extracting intermediate variables documents the types and intent: a 'maker' holding getMaker's Maker, a 'transformer' holding what the maker makes, then the String the transformer gives.

Each line is independently understandable. Variable names document what each intermediate value represents. Debugging can inspect each step. Code review can verify each type transition.

How to fix it

Extract intermediate variables to break the chain: give each delegate a call returns its own named variable, and call that. Single-level chaining remains valid. The fix in the [EXAMPLE] section below shows both: a single-level chain 'getTransformer(UPPER_TRANSFORM)("single level is fine")', and the same call decomposed through a named 'transformer'.

See also

  • E11013: excessive expression complexity
  • E11011: excessive nesting

Examples

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

Fails with E11069: nested call chain exceeds one level - extract intermediate variable for readability

#!ek9
defines module error.e11069.bad

  defines function

    Transformer() as pure abstract
      -> input as String
      <- output as String?

    UpperTransformer() extends Transformer as pure
      -> input as String
      <- output as String: input.upperCase()

    Maker() as abstract
      -> seed as String
      <- transformer as Transformer?

    SimpleMaker() extends Maker
      -> seed as String
      <- transformer as Transformer: UpperTransformer

    getMaker()
      -> kind as String
      <- maker as Maker: SimpleMaker

      if kind == "simple"
        maker: SimpleMaker

    //Two levels deep: getMaker returns Maker, Maker returns Transformer, Transformer returns String
    checkTwoLevelChain()
      <- result as String?

      result: getMaker("simple")("seed")("text")

Fixed: Name the intermediate result

#!ek9
defines module error.e11069.good

  defines constant

    //'upper' appeared three times, which is E11065 (repeated magic literal). Naming it keeps this
    //positive control clean - it exists to say nothing about call-chain depth, not to trip a
    //different rule.
    UPPER_TRANSFORM <- "upper"

  defines function

    Transformer() as pure abstract
      -> input as String
      <- output as String?

    UpperTransformer() extends Transformer as pure
      -> input as String
      <- output as String: input.upperCase()

    getTransformer()
      -> mode as String
      <- transformer as Transformer: UpperTransformer

      if mode == "lower"
        transformer: UpperTransformer

    //Single-level chaining: valid functional pattern
    checkSingleLevelChain()
      <- result as String?
      result: getTransformer(UPPER_TRANSFORM)("single level is fine")

    //Single-level chaining with objectAccess: also valid
    checkSingleLevelWithChain()
      <- result as String?
      result: getTransformer(UPPER_TRANSFORM)("  chained  ").trim()

    //Direct function call: not a chain at all
    checkDirectCall()
      <- result as String?
      processor as Transformer: UpperTransformer
      result: processor("direct delegate")

    //Decomposed two-level: the correct way to handle depth
    checkDecomposedChain()
      <- result as String?
      transformer <- getTransformer(UPPER_TRANSFORM)
      result: transformer("decomposed is clear")

Questions and answers

These Q&A entries show E11069 and its fix: