Question: Why does a QUERY/HEADER/PATH service parameter need a name qualifier in EK9?

What triggers E07710 SERVICE_HTTP_PARAM_NEEDS_QUALIFIER?

Why must I name the QUERY string for a service parameter?

How do I bind a query string parameter to a method argument in EK9?

Answer:
An EK9 service parameter bound with QUERY, HEADER, or PATH must declare the wire name it binds to as a String qualifier. The method-parameter identifier is your local variable name; the qualifier string is the actual HTTP query key, header name, or path segment name. Without the qualifier the compiler cannot know which request value to extract, so it raises E07710.

Fix: add the qualifier string. For a query string ?q=...:
  -> term as String :=: QUERY "q"
For an HTTP header:
  -> token as String :=: HEADER "Authorization"
For a named path segment, the qualifier follows the :/{seg} variable in the URI.

The REQUEST qualifier is the exception - it binds the whole HTTPRequest and must NOT carry a name (a name there triggers E07720).

See Q945 for HTTP verbs and URI params. See Q658 for path parameter binding. See Q846 for valid service parameter types.

defines module qa.webdeep.service.param.qualifier

  defines constant

    JSON_TYPE <- "application/json"

  defines service

    <?-
      Service demonstrating named QUERY and HEADER parameter binding.
      Each parameter declares the wire name it reads from, so the
      compiler can extract the right request value (no E07710).
    -?>
    SearchService :/search open

      // GET /search/results?q=... with an Authorization header
      find() as GET for :/results
        ->
          term as String :=: QUERY "q"
          token as String :=: HEADER "Authorization"
        <- response as HTTPResponse: (capturedTerm: term, capturedToken: token) with trait of HTTPResponse
          override content()
            <- rtn as String: `{"query": "${capturedTerm}", "authorized": ${capturedToken?}}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_TYPE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    SearchApp
      register SearchService()

  defines program

    ServiceParamQualifierDemo()
      stdout <- Stdout()
      stdout.println("Named service parameter binding:")
      stdout.println("  GET /search/results?q=... -> QUERY \"q\" binds to term")
      stdout.println("  Header Authorization      -> HEADER \"Authorization\" binds to token")

Java Spring: @RequestParam("q"), @RequestHeader("Authorization"), @PathVariable("id") - the wire name is an annotation argument, omittable so it defaults to the Java parameter name (silent at compile time, surprises at runtime). Python Flask, Go gorilla/mux: the wire key is a plain string in code, never checked. EK9: the wire name is a mandatory part of the binding syntax, enforced at compile time (E07710), so a parameter can never silently bind to the wrong request value.

E07710 HEADER PATH QUERY REST binding http name parameter qualifier service

->
          term as String :=: QUERY "q"

Explanation: A QUERY (or HEADER or PATH) binding must state the wire name it reads from. Without it, the compiler cannot tell which query key, header, or path segment to extract, so it raises E07710. Add the name qualifier - 'QUERY "q"' binds the ?q= query string to the 'term' parameter. (Only REQUEST takes no name.) See ek9 -h E07710 for details.


Question: Why does EK9 reject a qualifier name on a REQUEST or CONTENT service parameter?

What triggers E07720 SERVICE_HTTP_PARAM_QUALIFIER_NOT_ALLOWED?

Why can't I add a name after :=: REQUEST or :=: CONTENT?

Which HTTP service bindings take a qualifier name and which do not?

Answer:
EK9 service parameters bind to one HTTP source via :=:. Only PATH, QUERY and HEADER bindings take an extra qualifier name (the URI segment, query key or header name) so the runtime knows which value to extract. REQUEST and CONTENT bind to the whole request object or the whole request body respectively, so there is nothing to name. Adding a quoted qualifier to REQUEST or CONTENT triggers E07720 (SERVICE_HTTP_PARAM_QUALIFIER_NOT_ALLOWED) at the EXPLICIT_TYPE_SYMBOL_DEFINITION phase.

CORRECT
  -> request as HTTPRequest :=: REQUEST
  -> body as String :=: CONTENT

INCORRECT
  -> request as HTTPRequest :=: REQUEST "x"   // E07720
  -> body as String :=: CONTENT "payload"      // E07720

The fix is simply to delete the qualifier name. Contrast with E07710 (missing qualifier), which fires when PATH, QUERY or HEADER lacks the name they DO require.

See Q658 for path parameter binding. See Q868 for service parameter types.

defines module qa.webdeep.service.param.qualifier

  defines constant

    JSON_RESPONSE <- "application/json"

  defines service

    <?-
      Service showing the bindings that do NOT take a qualifier name.
      REQUEST binds the whole request, CONTENT binds the whole body.
    -?>
    EchoService :/echo open

      // REQUEST binds the full HTTPRequest - no qualifier name allowed.
      describe() as GET for :/details
        -> request as HTTPRequest :=: REQUEST
        <- response as HTTPResponse: (incoming: request.content()) with trait HTTPResponse
          override content()
            <- rtn as String: `{"request": "${incoming}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_RESPONSE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      // CONTENT binds the whole request body - no qualifier name allowed.
      operator += :/
        -> body as String :=: CONTENT
        <- response as HTTPResponse: (received: body) with trait HTTPResponse
          override content()
            <- rtn as String: `{"received": "${received}"}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: JSON_RESPONSE
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    EchoApp
      register EchoService()

  defines program

    QualifierDemo()
      stdout <- Stdout()
      stdout.println("No qualifier name: :=: REQUEST and :=: CONTENT")
      stdout.println("Qualifier name required: :=: PATH, :=: QUERY, :=: HEADER")

Java Spring: @RequestBody and the HttpServletRequest argument take no name, while @RequestParam/@PathVariable/@RequestHeader take a key string; mixing them up is silently accepted or fails at runtime. ASP.NET / Flask / Go behave similarly with no compile-time guard. EK9 enforces the distinction at compile time: REQUEST and CONTENT must have no qualifier, PATH/QUERY/HEADER must have one.

CONTENT E07720 HEADER QUERY REQUEST binding http parameter qualifier service

service parameter qualifier
E07720
REQUEST and CONTENT binding

-> request as HTTPRequest :=: REQUEST "not-required"

Explanation: REQUEST binds the whole HTTP request and CONTENT binds the whole request body, so neither accepts a qualifier name. The quoted qualifier (only valid for PATH, QUERY and HEADER, which need to name the segment/key/header) is rejected as E07720. Remove the qualifier so the binding reads ':=: REQUEST'. See ek9 -h E07720 for details.


Question: Why does EK9 reject a service method that declares a return but has no implementation body?

What triggers E07740 - implementation not provided, services cannot be abstract?

Why can't an EK9 service method be left abstract with just a return declaration?

How do I fix 'implementation not provided' on a service endpoint in EK9?

Answer:
Services in EK9 are CONCRETE entry points - every endpoint method must supply a real implementation body. Declaring only the return (for example '<- response as HTTPResponse?') with no statements that build the response leaves the method effectively abstract, and EK9 raises E07740 because services cannot be abstract. The runtime must be able to invoke every route, so there is no such thing as an abstract service method.

The fix is to provide a body that materialises the HTTPResponse, typically a dynamic class implementing the HTTPResponse trait with overrides for content, status, contentType, cacheControl and contentLanguage.

E07740 differs from E07800: E07800 fires when there is no return declaration at all, whereas E07740 fires when the return is declared but no implementation is provided. See Q685 for service method body rules. See Q869 for the missing-return case (E07800).

defines module qa.web.servicemethodbody

  defines service

    StatusService :/status open

      //CORRECT: the endpoint provides a concrete implementation body that
      //materialises an HTTPResponse. Leaving only the '<- response as HTTPResponse?'
      //declaration with no body would raise E07740 (services cannot be abstract).
      health() as GET for :/health
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"status": "healthy"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    StatusApp
      register StatusService()

  defines program

    ServiceMethodBodyDemo()
      stdout <- Stdout()
      stdout.println("Service registered with application")
      stdout.println("GET /status/health returns a concrete HTTPResponse")

Java (JAX-RS/Spring): a controller method can compile as an abstract or interface declaration with no body, deferring the implementation - the gap surfaces only at runtime wiring. Kotlin/C#: abstract handler signatures are equally legal in base classes. EK9: a service is a dedicated concrete entry-point construct, so a route declared without an implementation body is rejected at compile time with E07740 - there are no abstract service methods.

E07740 HTTPResponse abstract body concrete endpoint implementation method rest service web

service method body
E07740
services cannot be abstract

health() as GET for :/health
  <- response as HTTPResponse?

Explanation: Declaring only the return type with no statements that build the response leaves the service method without an implementation, which EK9 treats as abstract and rejects with E07740 because services cannot be abstract. Provide a concrete body that materialises the HTTPResponse (a dynamic class implementing the HTTPResponse trait). See ek9 -h E07740 for details.


Question: Why must a service parameter bound with :=: REQUEST be typed as HTTPRequest in EK9?

What triggers E07770 SERVICE_INCOMPATIBLE_PARAM_TYPE_REQUEST?

Why can't I bind :=: REQUEST to a String parameter?

What type does the :=: REQUEST binding require in an EK9 service?

Answer:
The :=: REQUEST binding hands the service method the entire HTTP request object, so the parameter it binds to must be typed as HTTPRequest. Binding REQUEST to any other type (String, Integer, a record, etc.) triggers E07770 because the compiler cannot deliver a full request into a parameter that is not an HTTPRequest.

CORRECT PATTERN
  handle() as GET for :/
    -> request as HTTPRequest :=: REQUEST
The single parameter receives the whole request.

INCORRECT PATTERN
  handle() as GET for :/
    -> request as String :=: REQUEST
String cannot receive a full request, so E07770 is raised.

This is the mirror of E07790 (HTTPRequest used with a non-REQUEST binding such as PATH or QUERY): REQUEST and HTTPRequest must always go together, and a REQUEST parameter must stand by itself in the method. For individual values use :=: PATH, :=: QUERY or :=: HEADER with simple parseable types like Integer or String.

See Q868 for the inverse restriction. See Q202 for parameter binding. See Q112 for a full service example.

defines module qa.webdeep.service.request.binding

  defines constant

    JSON_TYPE <- "application/json"

  defines service

    <?-
      A service whose handler needs the full request object.
      The :=: REQUEST binding requires an HTTPRequest parameter.
    -?>
    EchoService :/echo open

      handle() as GET for :/
        -> request as HTTPRequest :=: REQUEST
        <- response as HTTPResponse: (body: request.content()) with trait HTTPResponse
          override content()
            <- rtn as String: `{"received": "${body}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_TYPE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    EchoApp
      register EchoService()

  defines program

    RequestBindingDemo()
      stdout <- Stdout()
      stdout.println("REQUEST binding requires an HTTPRequest parameter.")
      stdout.println("For individual values use PATH, QUERY or HEADER with simple types.")

Java Spring: a controller can inject HttpServletRequest or bind individual values via @RequestParam/@PathVariable, with no compile-time link between the binding annotation and the parameter type. C# ASP.NET: HttpRequest and model binding mix freely. Go: handlers always receive *http.Request and parse manually. EK9: the binding keyword and the parameter type are checked together at compile time, so REQUEST is only ever allowed against an HTTPRequest parameter.

E07770 HTTPRequest REQUEST binding http parameter service type

Explanation: The :=: REQUEST binding delivers the entire HTTP request, so its parameter must be typed as HTTPRequest. Binding REQUEST to a String (or any non-HTTPRequest type) cannot receive a full request and raises E07770. Type the parameter as HTTPRequest, or for individual values use :=: PATH/QUERY/HEADER with simple types. See ek9 -h E07770 for details.


Question: Why can't I mix HTTPRequest with other parameters in an EK9 service operation?

What triggers E07780 SERVICE_REQUEST_BY_ITSELF?

Why must HTTPRequest be the only parameter on a service operation?

How do I get a path or query value when I also need the full HTTPRequest?

Answer:
When a service operation takes the full request via :=: REQUEST (an HTTPRequest), that parameter must be the ONLY parameter on the operation. Adding any second parameter — a CONTENT binding, a PATH segment, anything — triggers E07780 (SERVICE_REQUEST_BY_ITSELF).

The rule exists because HTTPRequest already gives you the entire request — path segments, query string, headers and body. Mixing it with individually-bound parameters duplicates the binding and creates two sources of truth for the same data.

CORRECT PATTERN
  process() as POST for :/process
    -> request as HTTPRequest :=: REQUEST
Read the body, headers and query values from the request inside the method body via request.content() and friends.

INCORRECT PATTERN
  process() as POST for :/process
    ->
      request as HTTPRequest :=: REQUEST
      body as String :=: CONTENT
The extra body parameter alongside the full request raises E07780.

See Q202 for parameter binding. See Q868 for valid path-parameter types.

defines module qa.webdeep.service.request.only

  defines constant

    JSON_RESPONSE <- "application/json"

  defines service

    <?-
      Operation that binds the FULL request via :=: REQUEST.
      HTTPRequest is the only parameter, so E07780 is not raised.
      Any path/query/body value is read from the request inside the body.
    -?>
    ItemService :/items open

      process() as POST for :/process
        -> request as HTTPRequest :=: REQUEST
        <- response as HTTPResponse: (request: request) with trait HTTPResponse
          override content()
            <- rtn as String: `{"received": "${request.content()}"}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: JSON_RESPONSE
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    ItemApp
      register ItemService()

  defines program

    HttpRequestOnlyDemo()
      stdout <- Stdout()
      stdout.println("HTTPRequest must be the ONLY parameter on an operation.")
      stdout.println("Read path/query/body values from the request inside the body.")

Java Spring: a handler may freely mix HttpServletRequest with @PathVariable/@RequestParam — nothing prevents the duplication. C# ASP.NET: HttpContext plus bound parameters coexist. Go net/http: you read *http.Request and also parse mux.Vars(r) together. EK9: the compiler forbids the mixture at compile time — if you take the full HTTPRequest you take only that and extract the rest from it.

E07780 HTTPRequest REQUEST binding http parameter rest service

      process() as POST for :/process
        ->
          request as HTTPRequest :=: REQUEST
          body as String :=: CONTENT

Explanation: An operation that binds the full request with :=: REQUEST must have HTTPRequest as its ONLY parameter. The extra CONTENT-bound body parameter makes the operation take more than one parameter, so E07780 fires. Drop the extra parameter and read the body from the request inside the method body via request.content(). See ek9 -h E07780 for details.


Question: Why must a CONTEXT-bound service parameter be typed HTTPContext in EK9?

What triggers E07791 wrong type for CONTEXT binding?

Why can't I bind :=: CONTEXT to a String parameter in an EK9 service?

What is the difference between CONTENT and CONTEXT binding in EK9 services?

Answer:
The CONTEXT qualifier gives a service operation access to the request context (authenticated principal, security state, connection metadata), so the bound parameter MUST be typed HTTPContext. Binding :=: CONTEXT to a String (or any other type) triggers E07791 because the framework supplies a context object, not a parsed value.

CORRECT PATTERN
  info() as GET for :/data
    -> ctx as HTTPContext :=: CONTEXT

INCORRECT PATTERN
  info() as GET for :/data
    -> ctx as String :=: CONTEXT

Do not confuse CONTENT with CONTEXT. CONTENT binds the request body, which IS a String; CONTEXT binds request metadata, which is an HTTPContext. For individual request values use the specific access qualifiers (:=: QUERY, :=: PATH).

See Q202 for parameter binding. See Q868 for parameter type rules.

defines module qa.webdeep.service.context.binding

  defines constant

    JSON_RESPONSE <- "application/json"

  defines service

    <?-
      Service using CONTEXT binding correctly: the parameter is typed
      HTTPContext, which is what the framework supplies for :=: CONTEXT.
    -?>
    InfoService :/info open

      info() as GET for :/data
        ->
          ctx as HTTPContext :=: CONTEXT
        <- response as HTTPResponse: (capturedCtx: ctx) with trait HTTPResponse
          override content()
            <- rtn as String: `{"hasContext": ${capturedCtx?}}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_RESPONSE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    InfoApp
      register InfoService()

  defines program

    ContextBindingDemo()
      stdout <- Stdout()
      stdout.println("CONTEXT binds an HTTPContext (request metadata).")
      stdout.println("CONTENT binds a String (the request body).")

Java Spring: SecurityContextHolder / Principal injected as method args, validated only at runtime. C# ASP.NET: HttpContext available ambiently, no compile-time type checking of binding. Go: context.Context passed manually with no framework guard. EK9: the compiler enforces at compile time that a CONTEXT-bound parameter is exactly HTTPContext, eliminating mismatched-binding bugs before the service runs.

CONTENT CONTEXT E07791 HTTPContext binding request service type

->
          ctx as HTTPContext :=: CONTEXT

Explanation: The CONTEXT qualifier supplies the request context object (principal, security state, connection metadata), so the parameter must be typed HTTPContext. Binding :=: CONTEXT to a String triggers E07791. CONTENT binds the body (a String); CONTEXT binds metadata (an HTTPContext) — do not confuse them. See ek9 -h E07791 for details.


Question: Why can't I mix HTTPContext with QUERY or PATH parameters in an EK9 service method?

What triggers E07792 HTTPContext mixed with other params?

Why must HTTPContext be the only parameter in a service method?

How do I access query and path values when using HTTPContext in EK9?

Answer:
When an EK9 service method binds the full request via :=: CONTEXT, the HTTPContext parameter must be the ONLY parameter. HTTPContext already exposes the complete request - query string, path segments, headers, body - so adding individual :=: QUERY or :=: PATH bindings alongside it is redundant and ambiguous, raising E07792.

You have two valid choices:
1. Use HTTPContext ALONE and extract everything you need from it inside the method body via its request accessors.
2. Use only individual qualified parameters (:=: PATH, :=: QUERY, :=: HEADER, :=: CONTENT) and do NOT take an HTTPContext at all.

See Q202 for parameter binding and Q945 for parameter qualifiers.

defines module qa.webdeep.service.httpcontext

  defines constant

    JSON_TYPE <- "application/json"

  defines service

    <?-
      Search service demonstrating the correct use of HTTPContext.
      The method binds the full request via :=: CONTEXT and uses ONLY
      that single parameter. Any query or path values are read from the
      context inside the method body - never via a second :=: QUERY/PATH
      binding, which would raise E07792.
    -?>
    SearchService :/search open

      search() as GET for :/results
        -> ctx as HTTPContext :=: CONTEXT
        <- response as HTTPResponse: (reqId: ctx.requestId()) with trait HTTPResponse
          override content()
            <- rtn as String: `{"requestId": "${reqId}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_TYPE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    SearchApp
      register SearchService()

  defines program

    HttpContextDemo()
      stdout <- Stdout()
      stdout.println("HTTPContext must be the ONLY service-method parameter.")
      stdout.println("Read query and path values from the context inside the body,")
      stdout.println("never via a second :=: QUERY or :=: PATH binding (E07792).")

Java Spring: you may freely combine HttpServletRequest with @RequestParam/@PathVariable in the same handler - no compiler objection, even though the request object already contains those values. JAX-RS allows @Context HttpServletRequest beside @QueryParam. EK9 instead makes the redundancy a compile-time error (E07792): the all-encompassing HTTPContext and individual bindings are mutually exclusive, so a method has exactly one, unambiguous binding strategy.

CONTEXT E07792 HTTPContext PATH QUERY binding parameter service web

HTTPContext binding
E07792
service parameter mixing

        ->
          ctx as HTTPContext :=: CONTEXT
          term as String :=: QUERY "q"

Explanation: HTTPContext (:=: CONTEXT) already provides the entire request, so adding a second :=: QUERY binding alongside it is redundant and ambiguous, triggering E07792. See ek9 -h E07792 for details.


Question: Why can't I bind HTTPContext to a QUERY parameter in an EK9 service?

What triggers E07793 when using HTTPContext in a service parameter?

Which access type must HTTPContext use in EK9 services?

Why does EK9 reject HTTPContext with :=: QUERY, PATH, HEADER or CONTENT?

Answer:
HTTPContext is the whole-request context object, so it can only be bound with the :=: CONTEXT access type. Binding it with :=: QUERY, :=: PATH, :=: HEADER or :=: CONTENT triggers E07793 because those access types extract a single named value from the URI, path, headers or body — they cannot reconstruct the full context object.

CORRECT PATTERN
  info() as GET for :/info
    -> ctx as HTTPContext :=: CONTEXT
HTTPContext bound with CONTEXT gives access to the entire request context.

If you only need a single query value, bind a simple type instead:
  search() as GET for :/search
    -> term as String :=: QUERY "q"

See Q202 for parameter binding. See Q868 for the related HTTPRequest restriction. See Q112 for a full service example.

defines module qa.webdeep.service.httpcontext.access

  defines constant

    PLAIN_TEXT <- "text/plain"

  defines service

    <?-
      Service showing the two correct bindings: HTTPContext must use
      :=: CONTEXT, while a single query value uses a simple String type.
    -?>
    Info :/info open

      context() as GET for :/context
        -> ctx as HTTPContext :=: CONTEXT
        <- response as HTTPResponse: (capturedId: ctx.requestId()) with trait HTTPResponse
          override content()
            <- rtn as String: `context request id ${capturedId}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: PLAIN_TEXT
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      search() as GET for :/search
        -> term as String :=: QUERY "q"
        <- response as HTTPResponse: (capturedTerm: term) with trait HTTPResponse
          override content()
            <- rtn as String: `searched for ${capturedTerm}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: PLAIN_TEXT
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    InfoApp
      register Info()

  defines program

    HttpContextAccessDemo()
      stdout <- Stdout()
      stdout.println("HTTPContext must bind with :=: CONTEXT")
      stdout.println("A single query value binds with :=: QUERY using a simple type")

Java Spring: any controller argument can be annotated freely (@RequestParam, @RequestBody) with no compile-time check that the type matches the binding source. C# ASP.NET: model binding is convention-based and resolved at runtime. Go/Python: the request context is read manually with no binding metadata. EK9: the compiler enforces at compile time that HTTPContext is only ever bound with :=: CONTEXT, so a context object can never be wired to a query, path, header or body source.

CONTEXT E07793 HTTPContext QUERY access binding http rest service

HTTPContext access type
E07793
service parameter binding

Explanation: HTTPContext is the whole-request context object and can only be bound with the :=: CONTEXT access type. Binding it with :=: QUERY (or PATH, HEADER, CONTENT) extracts a single named value and cannot produce a context object, so the compiler raises E07793. Use :=: CONTEXT for HTTPContext, or bind a simple type like String for a query value. See ek9 -h E07793 for details.


Question: Why can't I mark a captured variable as 'sanitized' in a dynamic function?

What triggers E07941 SANITIZED_NOT_ON_CAPTURED?

How do I sanitize a value before capturing it into a closure?

Why is 'sanitized' rejected on a dynamic function or class capture list?

Answer:
The 'sanitized' modifier cannot be applied to a captured variable in a dynamic function or dynamic class (E07941). By the time a value is captured into a closure it has already crossed the trust boundary, so re-asserting sanitization there is both meaningless and misleading.

Sanitization is a property of the ENTRY POINT - the function or method parameter where untrusted external data first enters the system. Mark that parameter 'sanitized' and the compiler injects a defensive sanitizing copy. The dynamic function then captures the already-clean value with a plain capture (no modifier).

WRONG
  build()
    -> raw as String
    fn <- (sanitized raw) is Formatter as pure function ...   // E07941

RIGHT
  build()
    -> raw as sanitized String          // sanitize at the boundary
    fn <- (raw) is Formatter as pure function ...   // capture the clean value

See Q950 for the full set of places 'sanitized' is restricted (captures E07941, declarations E07943).

defines module qa.sanitizeddeep.captureboundary

  defines function

    <?-
      Abstract function contract the dynamic function will implement.
    -?>
    Formatter as pure abstract
      -> text as String
      <- output as String?

    <?-
      THE FIX: sanitize at the ENTRY POINT (the parameter), not at the capture.
      The compiler injects a sanitizing defensive copy for 'rawInput' here.
      The dynamic function then captures the already-clean value with a plain
      capture list (no 'sanitized' modifier - that would be E07941).
    -?>
    buildPrefixer() as pure
      -> rawInput as sanitized String
      <- made as Formatter?

      made: (prefix: rawInput) is Formatter as pure function
        output: `${prefix}: ${text}`

  defines program

    SanitizedCaptureDemo()
      stdout <- Stdout()

      formatter <- buildPrefixer("user-data")
      stdout.println(formatter("hello"))
      stdout.println("sanitize at the parameter, capture the clean value")

Java/Kotlin/Python: no language-level taint tracking - a closure can capture tainted data and developers must remember to sanitize manually somewhere, with no compiler check. EK9 forces sanitization to live at the single entry-point parameter and rejects it on captures at compile time (E07941), so the trust boundary stays in exactly one obvious place.

E07941 boundary capture captured closure dynamic function sanitized security trust

sanitized on capture
E07941
dynamic function capture

made: (prefix: sanitized rawInput) is Formatter as pure function

Explanation: 'sanitized' cannot be applied to a captured variable - it belongs on the entry-point parameter where untrusted data enters. See ek9 -h E07941 for details.


Question: Why does EK9 reject a coalescing expression where both sides are literals?

What triggers E08094 when using <? or >? with two literal values?

How do I fix a coalescing minimum or maximum that has a predetermined result?

Answer:
EK9 detects when both operands of a coalescing operator (<?, >?, <=?, >=?, ?:, ??) are literal values and raises E08094. Because both sides are known at compile time, the coalescing result is predetermined - the expression is dead code and almost always a copy-paste error or unfinished placeholder.

THE PROBLEM
The coalescing minimum '100 <? 200' always yields 100; the maximum '100 >? 200' always yields 200. Writing this is no different from assigning the answer directly, so the operator serves no purpose.

CORRECT PATTERN
Keep at least one operand a variable or named constant so the result genuinely depends on runtime values:
  best <- onlinePrice <? FLOOR_PRICE
  cap  <- requested >? MAX_LIMIT
If you really want a fixed value, assign it directly: result: 100.

See Q638 for literal-vs-literal comparison detection (E08082). See Q963 for how the <? coalescing minimum operator works.

defines module qa.comparison.constant.coalescing

  defines constant

    FLOOR_PRICE <- 50

    MAX_LIMIT <- 200

  defines function

    <?-
      Correct: coalescing minimum between a variable and a named constant.
      Replacing onlinePrice with a literal would trigger E08094.
    -?>
    bestPrice() as pure
      -> onlinePrice as Integer
      <- best as Integer: onlinePrice <? FLOOR_PRICE

    <?-
      Correct: coalescing maximum between a variable and a named constant.
    -?>
    cappedRequest() as pure
      -> requested as Integer
      <- cap as Integer: requested >? MAX_LIMIT

  defines program

    ConstantCoalescingDemo()
      stdout <- Stdout()

      stdout.println(`Best price: ${bestPrice(75)}`)
      stdout.println(`Best price: ${bestPrice(40)}`)
      stdout.println(`Capped: ${cappedRequest(150)}`)
      stdout.println(`Capped: ${cappedRequest(250)}`)

Java: has no coalescing minimum/maximum operator and no detection of predetermined expressions; Math.min(100, 200) compiles silently. Kotlin/Swift: chained ?: and ?? with two constants compile without warning. Rust: clippy flags some absurd constant comparisons but not coalescing. EK9: compile-time error (E08094) whenever both coalescing operands are literals.

E08094 coalescing code comparison constant dead literal maximum minimum pattern predetermined

constant coalescing
predetermined expression
E08094

Explanation: Both operands of the <? coalescing minimum are literals, so the result is fixed at compile time (always 100) - dead code that usually means a copy-paste slip or unfinished placeholder. Keep at least one side a variable or named constant, or assign the intended value directly. See ek9 -h E08094 for details.


Question: Why does EK9 reject two 'register' lines for the same abstract component type?

What triggers E08230 duplicate registration in an application?

Why can I only register one implementation per abstract type per application?

How do I wire two implementations of one interface in EK9?

Answer:
Within a single application block each abstract component type may have exactly ONE concrete registration. A second 'register ... as <SameAbstractType>' raises E08230 (duplicate registration). The reason is resolution: when a program injects 'cache as Cache!', the compiler must bind that injection point to exactly one concrete implementation. Two registrations for Cache would make that binding ambiguous, so EK9 rejects it at compile time rather than picking arbitrarily or failing at runtime.

The fix is to keep one binding per abstract type in the application. If you genuinely need a different implementation (production vs test), declare a SEPARATE application, each with its own single registration, and select the application with 'with application of'.

See Q673 for missing registration (E08210). See Q674 for the same rule from the ambiguity angle. See Q671 for circular dependencies.

defines module qa.divalidation.duplicateregistration

  defines component

    <?-
      Abstract cache contract.
    -?>
    Cache as abstract

      store() as abstract
        ->
          key as String
          payload as String

      default operator ?

    <?-
      Redis-backed cache for production.
    -?>
    RedisCache extends Cache

      override store()
        ->
          key as String
          payload as String
        stdout <- Stdout()
        stdout.println(`Redis store ${key}=${payload}`)

      default operator ?

    <?-
      In-memory cache for tests.
    -?>
    MemoryCache extends Cache

      override store()
        ->
          key as String
          payload as String
        stdout <- Stdout()
        stdout.println(`Memory store ${key}=${payload}`)

      default operator ?

  defines application

    <?-
      Production wiring: exactly ONE registration for Cache.
    -?>
    ProdApp
      register RedisCache() as Cache

    <?-
      Test wiring: same abstract type, different implementation,
      in a SEPARATE application. One binding each, no E08230.
    -?>
    TestApp
      register MemoryCache() as Cache

  defines program

    DuplicateRegistrationDemo() with application of ProdApp
      stdout <- Stdout()

      cache as Cache!
      cache.store("user:1", "Alice")

      stdout.println("One abstract type = one registration per application")

Java/Spring: two beans of one type need @Primary or @Qualifier, otherwise NoUniqueBeanDefinitionException at startup (runtime). Guice: a duplicate bind throws CreationException at injector build time. Kotlin/Koin: last definition silently overrides earlier ones. EK9: a duplicate registration for the same abstract type in one application is a compile-time error (E08230); use separate applications for separate wiring.

DI E08230 abstract ambiguous application binding component duplicate inject register registration

duplicate registration
E08230
one binding per abstract type

BrokenApp
      register RedisCache() as Cache
      register MemoryCache() as Cache

Explanation: Both 'register' lines bind the same abstract type Cache inside one application, so an injection point 'cache as Cache!' has two candidate implementations and cannot be resolved unambiguously - this raises E08230. Keep one registration per abstract type per application; if you need a different implementation, put it in a separate application (ProdApp vs TestApp) and select it with 'with application of'. See ek9 -h E08230 for details.


Question: Why does EK9 reject a method or function that has too many statements?

What triggers E11012 EXCESSIVE_STATEMENT_COUNT in EK9?

How do I fix a function the compiler says has too many statements?

What is the statement-count limit for functions and methods in EK9?

Answer:
EK9 measures a raw SIZE metric - the number of statements in each function, method, operator and service operation - separately from cyclomatic complexity (E11010). Each variable declaration, assignment, control-flow entry, throw and call counts as one statement. When the count exceeds the threshold the compiler raises E11012. The thresholds are: operators 50, methods 100, functions 150, service operations 100.

A long function is doing too much, so the fix is to identify logical groups of statements and extract each into a well-named helper function with a single responsibility. The original function then becomes a short sequence of calls, and every unit drops well under the threshold.

Note E11012 is a pure size check: it fires even when each statement is trivial and the cyclomatic complexity is low. See Q696 for the complexity-limit catalogue. See Q1020 for refactoring techniques. See Q311 for quality checks.

defines module qa.quality.statement.count

  defines function

    //FIX: each logical sub-task lives in its own small focused function,
    //so no single function approaches the 150-statement threshold.

    sumValues() as pure
      -> values as List of Integer
      <- rtn as Integer: 0
      for value in values
        rtn: rtn + value

    averageValue() as pure
      ->
        values as List of Integer
        total as Integer
      <- rtn as Integer: 0
      count <- length values
      if count > 0
        rtn: total / count

    formatReport() as pure
      ->
        total as Integer
        average as Integer
      <- rtn as String: `total=${total} average=${average}`

    //The orchestrating function is now a short sequence of calls,
    //well under the statement-count limit.
    buildReport() as pure
      -> values as List of Integer
      <- rtn as String?
      total <- sumValues(values)
      average <- averageValue(values, total)
      rtn: formatReport(total, average)

  defines program

    StatementCountDemo()
      stdout <- Stdout()
      values <- [10, 20, 30, 40]
      stdout.println(buildReport(values))

Java: method length is unbounded; only optional tools (Checkstyle MethodLength, PMD ExcessiveMethodLength, SonarQube) flag long methods, and they emit warnings teams routinely suppress. Kotlin/Swift/Go: no built-in statement-count limit; detekt/SwiftLint can warn if configured. EK9: statement count is a hard compile error (E11012) per function/method/operator/service-operation, so an oversized unit simply does not compile.

E11012 count decompose extract function method quality size statement

excessive statement count
E11012
function extraction

buildReport()
  -> values as List of Integer
  <- rtn as String?
  // 160+ inline statements computing the total, the average and the
  // formatted text all in one body

Explanation: Putting 160+ statements directly in one function exceeds the function threshold of 150 (methods 100, operators 50, service operations 100), so the compiler raises E11012 - a size check distinct from cyclomatic complexity. Identify logical groups (sum, average, format) and extract each into its own named helper, then call them in sequence; every unit then falls under the threshold. See ek9 -h E11012 for details.


Question: Why does EK9 reject a module whose constructs are all unrelated?

What triggers E11017 poor module cohesion?

Why must I split a grab-bag utilities module in EK9?

How does EK9 measure module construct disconnection?

Answer:
EK9 measures how connected the constructs inside one module are. Two constructs are 'connected' when one references the other's type. A grab-bag module - String helpers next to maths helpers next to date helpers, none sharing any types - forms many disconnected groups. When a module exceeds ALL THREE thresholds at once (more than 88% disconnection AND more than 30 disconnected groups AND more than 60 constructs) EK9 raises E11017, because the module is really several modules glued together.

The fix is the Split Module refactoring: gather constructs that share types into focused modules (qa.text, qa.geometry, qa.time, ...), each with one clear purpose. Constructs that share no types probably do not belong together. The example below is one such cohesive module - every construct references the Point type, so its constructs form a single connected group and cohesion is high.

See Q314 for how EK9 measures cohesion and coupling. See Q311 for the full quality-check catalog.

defines module qa.geometry.points

  defines record

    <?-
      Shared type that binds every construct in this module together.
      Because the function and the program both reference Point, the
      module forms a single connected group - high cohesion, no E11017.
    -?>
    Point
      x as Float: Float()
      y as Float: Float()

      Point() as pure
        ->
          initialX as Float
          initialY as Float
        this.x :=? initialX
        this.y :=? initialY

      operator $ as pure
        <- rtn as String: `(${x}, ${y})`

      default operator ?

  defines function

    //Operates on the shared Point type, keeping the module cohesive.
    distanceFromOrigin() as pure
      -> point as Point
      <- rtn as Float: Float()
      sumOfSquares <- point.x * point.x + point.y * point.y
      rtn: sumOfSquares.sqrt()

  defines program

    ModuleCohesionDemo()
      stdout <- Stdout()
      origin <- Point(3.0, 4.0)
      stdout.println(`Point ${origin} is ${distanceFromOrigin(origin)} from origin`)

Java: package contents are unconstrained; cohesion is only reported (LCOM, package-tangle) by SonarQube/JDepend as advisory metrics, never enforced. Kotlin/C#/Go: no module-cohesion enforcement - a 'utils' grab-bag compiles silently. EK9: low module cohesion is a hard compiler error (E11017) once the disconnection, group-count, and construct-count thresholds are all exceeded, forcing the Split Module refactoring.

E11017 cohesion connected disconnection grab-bag group module quality split utilities


Question: Why does EK9 report E11018 for an unused captured variable in a dynamic function?

What triggers E11018 unused captured variable?

Why must every variable I capture in a closure be used in the body?

How do I fix an unused capture in a dynamic function?

Answer:
EK9 dynamic functions capture variables EXPLICITLY, listing each one in parentheses before the 'is' keyword. Because capture is explicit and by value, every captured variable represents a deliberate dependency that is copied into the closure. If a captured variable is never referenced inside the function body, that capture is dead weight: it adds an unnecessary dependency, copies a value for no reason, and signals a copy-paste or refactoring mistake. EK9 raises E11018 (unused captured variable) at compile time.

The fix is to remove the unused variable from the capture list. Keep only the variables the body actually reads. If you genuinely need the value later, use it in the body; otherwise drop it from the capture list entirely.

This sits alongside E08090 (unused local variable) and E08091 (unused parameter): EK9 treats unused captures, locals, and parameters all as compile-time errors so that declared dependencies and real usage never drift apart.

See Q53 for closure capture mechanics. See Q319 for unused closure capture detection. See Q311 for quality checks.

defines module qa.gettingStarted.unusedCapture

  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <- result as Float?

  defines program

    UnusedCaptureDemo()
      stdout <- Stdout()

      //THE FIX: capture ONLY the variables the body actually uses.
      //'factor' is read inside the body, so it belongs in the capture list.
      factor <- 10.0
      scaled <- (factor) is mathOperation as pure function
        result:=? x * factor + y

      stdout.println(`captured: ${scaled(3.0, 1.0)}`)

      //A value that is NOT needed by the body must NOT appear in the capture
      //list. Here 'offset' is used, so it is captured; nothing surplus is.
      offset <- 5.0
      biased <- (offset) is mathOperation as pure (result:=? x + y + offset)
      stdout.println(`biased: ${biased(1.0, 2.0)}`)

Java/Kotlin/JavaScript/Python: capture is implicit and unchecked - a lambda silently closes over any enclosing variable it references, and there is no notion of an 'unused capture' because nothing is captured unless referenced. The dual risk is accidental capture of large objects (memory leaks) with no compiler signal. EK9: capture is an EXPLICIT list, so an entry that the body never uses is unambiguously a mistake and is rejected at compile time via E11018, keeping the declared dependency set exactly equal to the used set.

E11018 capture closure dependency dynamic function quality unused

unused captured variable
E11018
closure capture

scaled <- (factor, stdout) is mathOperation as pure function
        result:=? x * factor + y

Explanation: The capture list includes 'unused', but the body only reads 'factor', 'x' and 'y'. An explicitly captured variable that the function body never references is dead weight - an unnecessary by-value copy and a false dependency - so EK9 raises E11018. Remove the unused entry from the capture list, keeping only the variables the body actually uses. See ek9 -h E11018 for details.


Question: Why does EK9 reject a function whose complexity and size are both individually within limits?

What triggers E11020 COMBINED_COMPLEXITY_SIZE?

Why does EK9 fail a function that passes E11010 and E11012 separately?

How do I fix a function that is both moderately complex and moderately large?

Answer:
EK9 checks complexity and statement count not only against their individual ceilings (E11010 for complexity, E11012 for statement count) but also against their COMBINATION. The combined score is (complexity / 45) x (statements / 150 for a function); if it exceeds 0.50 the compiler raises E11020 even though neither metric alone has broken its limit. A function at 73% complexity and 87% size scores 0.64 and fails. The fix is to decompose the one large branchy function into several small focused functions, each with low complexity and few statements, then compose them. This drives both ratios down so the product falls under 0.50. The research basis is NASA SATC work showing that modules which are both moderately complex AND moderately large have the lowest reliability.

See Q311 for the full list of quality checks and Q312 for how complexity is measured.

defines module qa.quality.combined.complexity.size

  defines constant

    CODE_NEW <- 0
    CODE_PAID <- 1
    CODE_SHIPPED <- 2

  defines function

    //THE FIX: small, focused functions keep BOTH complexity and statement
    //count low, so the combined (complexity/45) x (statements/150) score
    //stays well under the 0.50 threshold and E11020 never fires.

    classifyCode() as pure
      -> code as Integer
      <- level as String: "unknown"
      if code == CODE_NEW
        level: "new"
      else if code == CODE_PAID
        level: "paid"
      else if code == CODE_SHIPPED
        level: "shipped"

    describeCode() as pure
      -> level as String
      <- description as String: "no description"
      if level == "new"
        description: "awaiting payment"
      else if level == "paid"
        description: "ready to ship"
      else if level == "shipped"
        description: "in transit"

    formatReport() as pure
      ->
        level as String
        description as String
      <-
        report as String: `${level}: ${description}`

    //Composition function: trivial complexity, few statements.
    analyseOrder() as pure
      -> code as Integer
      <- report as String: ""
      level <- classifyCode(code)
      description <- describeCode(level)
      report: formatReport(level, description)

  defines program

    CombinedComplexitySizeDemo()
      stdout <- Stdout()
      codes <- [CODE_NEW, CODE_PAID, CODE_SHIPPED]
      for code in codes
        stdout.println(analyseOrder(code))

Java/Kotlin/Swift: the compiler never analyses the product of complexity and size; a method that is both moderately branchy and moderately long compiles silently and is only flagged later by optional tools (SonarQube, Detekt, SwiftLint) if a custom rule is configured, and even then as an advisory warning that can be suppressed. EK9 enforces the combined complexity-size threshold at compile time as a hard error (E11020), so the over-grown function simply will not build until it is decomposed.

E11010 E11012 E11020 combined complexity decompose maintainability quality size statements

combined complexity and size
E11020
function decomposition


Question: Why does EK9 suggest promoting an injected type to a field when it appears in three or more methods (E11041)?

What triggers E11041 INJECTION_PROMOTE_TO_FACADE?

Why does EK9 flag the same '!' injected type used in 3+ methods of one component?

How do I fix a component that re-injects the same service in every method?

Answer:
When the SAME injected type is requested via the method-local '!' suffix inside 3 or more methods of a single component, E11041 (INJECTION_PROMOTE_TO_FACADE) advises promoting that dependency to a component field. Repeating the local injection in every method is a smell: the dependency is clearly central to the component's purpose, so it belongs as a single declared field rather than being re-resolved on every call.

Fix: declare the dependency once as a field with '!' at component scope, then have each method use that field. If a component accumulates many such central dependencies, group the related collaborators behind one facade component and inject the facade instead.

See Q960 for the per-component injection-field limit (E11040). See Q311 for quality checks.

defines module qa.quality.injection.promote

  defines component

    //Abstract contract for the central dependency.
    AuditService as abstract
      record() as abstract
        -> message as String
      default operator ?

    ConsoleAudit extends AuditService
      override record()
        -> message as String
        Stdout().println("[AUDIT] " + message)
      default operator ?

    //THE FIX: the AuditService is central to this component, so it is
    //declared ONCE as a field with '!' rather than re-injected locally
    //inside every method. Each method simply uses the field.
    OrderProcessor

      audit as AuditService!

      submit()
        -> label as String
        audit.record(`submit ${label}`)

      cancel()
        -> label as String
        audit.record(`cancel ${label}`)

      refund()
        -> label as String
        audit.record(`refund ${label}`)

      default operator ?

  defines application

    OrderApp
      register ConsoleAudit() as AuditService

  defines program

    //CORRECT: the central dependency is a single field, used by all methods.
    PromoteInjectionDemo() with application of OrderApp
      stdout <- Stdout()
      processor <- OrderProcessor()
      processor.submit("order-1")
      stdout.println("Central dependency promoted to a single field")

Java Spring: @Autowired on a field is conventional, but nothing stops repeated method-parameter injection or ObjectProvider lookups in every method; detected only by SonarQube/ArchUnit if configured. C#/.NET: Seemann recommends constructor injection of central dependencies but it is advisory. Go Wire: no per-method guidance. EK9: the compiler observes the repetition of a single injected type across 3+ methods and steers you to promote it to a field (E11041).

E11041 component dependency facade field injection method promote quality

submit()
        -> label as String
        audit as AuditService!
        audit.record(label)

      cancel()
        -> label as String
        audit as AuditService!
        audit.record(label)

      refund()
        -> label as String
        audit as AuditService!
        audit.record(label)

Explanation: The same AuditService type is method-local injected ('!') inside three separate methods, signalling that it is central to the component. Promote it to a single component field declared once with '!', then have every method use that field. If many central dependencies pile up, group them behind one facade component. See ek9 -h E11041 for details.


Question: Why does EK9 require a dedicated constants file once a module has many constants?

Why must 10+ module constants live in a single file?

How do I consolidate a large constant vocabulary spread across files?

Answer:
EK9 raises E11067 when a multi-file module has 10 or more constants spread across 2 or more files. This volume of constants across multiple files signals that the module's vocabulary has grown large enough to deserve a single dedicated constants file.

WHY CONSOLIDATION MATTERS
A large, scattered constant vocabulary is a maintenance burden: no developer can see the full set of fixed values without opening multiple files, duplicates and near-duplicates hide easily, and the intent of 'this is where constants live' is lost.

THE FIX
Create one dedicated constants file for the module and move every 'defines constant' block into it. Group related constants together with doc comments. If the count is very high, ask whether the module is doing too much.

This example shows the consolidated end-state: all 12 module constants live in a single file, organised by concern, so the multi-file/high-volume condition never arises.

See Q863 for the related 3+ file consolidation check (E11066). See Q695 for named constants. See Q311 for quality checks.

defines module qa.quality.constants.volume

  defines constant

    <?-
      Retry and timeout configuration.
      Used by network and service components.
    -?>
    MAX_RETRIES <- 3
    RETRY_DELAY_MS <- 500
    CONNECTION_TIMEOUT_MS <- 5000
    READ_TIMEOUT_MS <- 8000

    <?-
      Endpoint configuration.
      Centralised so a host change touches one place only.
    -?>
    BASE_URL <- "https://api.example.com"
    HEALTH_PATH <- "/health"
    METRICS_PATH <- "/metrics"

    <?-
      Reporting and formatting constants.
      Used by the output and report functions.
    -?>
    SEPARATOR_LINE <- "----------------------------------------"
    INDENT_SPACES <- "    "
    PAGE_SIZE <- 50
    MAX_PAGES <- 20
    REPORT_TITLE <- "Service Status Report"

  defines function

    <?-
      Build a single report line using the shared formatting constants.
    -?>
    formatReportLine() as pure
      -> content as String
      <- rtn as String: INDENT_SPACES + content

    <?-
      Compute how many items a full report could contain
      using the consolidated paging constants.
    -?>
    maxReportItems() as pure
      <- rtn as Integer: PAGE_SIZE * MAX_PAGES

  defines program

    ConstantsVolumeDemo()
      stdout <- Stdout()

      stdout.println(SEPARATOR_LINE)
      stdout.println(formatReportLine(REPORT_TITLE))
      stdout.println(SEPARATOR_LINE)

      stdout.println(formatReportLine(`Base URL: ${BASE_URL}${HEALTH_PATH}`))
      stdout.println(formatReportLine(`Max retries: ${MAX_RETRIES}`))
      stdout.println(formatReportLine(`Connect timeout: ${CONNECTION_TIMEOUT_MS}ms`))
      stdout.println(formatReportLine(`Read timeout: ${READ_TIMEOUT_MS}ms`))
      stdout.println(formatReportLine(`Metrics at: ${METRICS_PATH}`))
      stdout.println(formatReportLine(`Retry delay: ${RETRY_DELAY_MS}ms`))
      stdout.println(formatReportLine(`Max report items: ${maxReportItems()}`))

      stdout.println(SEPARATOR_LINE)

Java/Kotlin/Go: there is no compiler enforcement of how constants are organised across files; teams rely on convention (a Constants class, a constants.py, a const block) and on reviewers spotting sprawl. EK9 enforces it at compile time: once a module crosses 2+ files AND 10+ constants, E11067 fails the build until the constants are consolidated into one dedicated file.

E11067 consolidate constant file module organization quality scattered vocabulary volume

constant consolidation
E11067
dedicated constants file


Question: Why does EK9 reject a deeply nested higher-order call chain like getMaker(x)(y)(z)?

What triggers E11069 EXCESSIVE_CALL_CHAIN?

Why can't I chain more than one functional call in EK9?

How do I fix a call applied to the result of a call applied to a call?

Answer:
EK9 allows single-level functional chaining (e.g. getTransformer("upper")("text") - a delegate-returning function whose delegate is then invoked) but rejects deeper nesting with E11069. A two-level chain such as getMaker("x")("seed")("text") forces inside-out mental evaluation: you must work out what getMaker("x") returns, then what calling that with ("seed") returns, then call THAT with ("text"). Each level hides a type transition. The fix is to extract each intermediate result into a named, typed local variable so every step is independently readable, inspectable, and reviewable. Single-level chaining remains valid and idiomatic.

See Q311 for quality checks.

defines module qa.quality.call.chain

  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

    //THE FIX: extract each intermediate result into a named, typed local.
    //getMaker returns a Maker, the Maker returns a Transformer, the
    //Transformer returns a String. Each step is now independently clear,
    //instead of one inside-out chain getMaker("simple")("seed")("text").
    buildResult()
      <- result as String?
      maker <- getMaker("simple")
      transformer <- maker("seed")
      result: transformer("text")

  defines program

    ExcessiveCallChainDemo()
      stdout <- Stdout()
      result <- buildResult()
      stdout.println(`Result is ${result}`)

JavaScript/curried FP: f(a)(b)(c)(d) is unlimited and unchecked, leaving the reader to trace types inside-out. Haskell/Scala: deep currying is idiomatic but relies on a type-aware editor to reveal intermediate types. C: the same cognitive problem as nested pointer dereferences (*(**fp)(x))(y). EK9: compile-time error (E11069) at one level beyond single chaining, forcing extraction into named intermediate variables that document each type.

E11069 call chain delegate extract higher-order intermediate nested quality

excessive call chain
E11069
intermediate variables

Explanation: The expression is a call applied to the result of a call applied to the result of a call - two levels of nesting beyond a plain call. This forces inside-out evaluation and hides each type transition. Extract each step into a named typed local (maker, transformer) so every line documents its type and the final call is single-level. Single-level chaining such as getTransformer("upper")("text") is fine. See ek9 -h E11069 for details.


Question: Why does EK9 reject a named argument whose name does not match the parameter?

What triggers E06250 when calling a function with named parameters?

Why must named call arguments use the exact declared parameter names in EK9?

How do I fix 'the order and naming of arguments must match parameters'?

Answer:
EK9 lets you pass arguments by name using 'name: value' syntax, which makes call sites self-documenting. When you do, every name must EXACTLY match a declared parameter name (case-sensitive) and stay in declaration order. Passing a name that does not exist on the signature - a typo, a guessed name, or a name left stale after a refactor - raises E06250.

The fix is to read the function or method signature and use the real parameter names. In the example, makeRange declares 'low' and 'high'; calling makeRange(low: 2, high: 10) compiles, while makeRange(low: 2, max: 10) fails because there is no parameter called 'max'.

See Q597 for function parameters. See Q942 for passing functions as parameters.

defines module qa.functionsAndMethods.namedarguments

  defines function

    //Two parameters with clear, documented names.
    makeRange() as pure
      ->
        low as Integer
        high as Integer
      <- rtn as Integer: high - low

  defines program

    NamedArgumentDemo()
      stdout <- Stdout()

      //CORRECT: named arguments match the declared parameter names exactly,
      //in declaration order. Self-documenting and compile-checked.
      span <- makeRange(low: 2, high: 10)
      stdout.println(`Span via named args: ${span}`)

      //Positional calls remain valid too when names are obvious.
      other <- makeRange(0, 5)
      stdout.println(`Span via positional args: ${other}`)

Python: keyword arguments are checked at runtime - a wrong keyword raises TypeError only when the call executes. Kotlin/C#: named arguments are compile-checked like EK9, a wrong name fails to compile. Java: has no named arguments at all, so the whole class of name-mismatch bugs is replaced by silent positional mistakes. EK9: named arguments are compile-time validated (E06250) and must match the declared names exactly and stay in order.

E06250 argument call function match named order parameter signature

named arguments
E06250
parameter name matching

Explanation: makeRange declares parameters 'low' and 'high', but the call uses 'max' as the second name. Named arguments must match the declared parameter names exactly and stay in declaration order, so the unknown name 'max' raises E06250. Use the real parameter name 'high'. See ek9 -h E06250 for details.


Question: Why does EK9 report E06320 when a dispatcher handler has a different number of parameters from the entry?

What triggers E06320 invalid number of parameters on a dispatcher?

Why must every dispatcher handler have the same parameter count as the entry?

How do I fix a dispatcher handler that takes too many parameters?

Answer:
A dispatcher distributes a single call across handler methods of the same name, selecting the handler by the runtime type(s) of the argument(s). Every handler must therefore have the SAME number of parameters as the dispatcher entry method. If the entry takes one parameter but a handler takes two (or zero), the compiler cannot line the arguments up at the dispatch point and reports E06320 (invalid number of parameters). The dispatcher entry itself must also declare one or two parameters; zero or more than two is rejected with the same error.

FIX: give every handler exactly the same parameter count as the entry. If a handler genuinely needs extra data, pass it into the dispatcher entry too (a two-parameter dispatcher), or carry it on the argument object, so all overloads stay aligned.

See Q619 for two-parameter dispatch and Q105 for method dispatch.

defines module qa.dispatcher.handler.parametercount

  defines class

    Shape as abstract
      area() as pure abstract
        <- rtn as Float?
      default operator ?

    Circle extends Shape
      override area() as pure
        <- rtn as Float: 3.14
      default operator ?

    Square extends Shape
      override area() as pure
        <- rtn as Float: 4.0
      default operator ?

    //CORRECT: the dispatcher entry takes one parameter, and EVERY handler
    //also takes exactly one parameter of the same arity. The handler is
    //selected at run time by the actual type of the single argument.
    AreaReporter
      report() as pure dispatcher
        -> shape as Shape
        <- rtn as String: `Shape area ${shape.area()}`

      report() as pure
        -> shape as Circle
        <- rtn as String: `Circle area ${shape.area()}`

      report() as pure
        -> shape as Square
        <- rtn as String: `Square area ${shape.area()}`

  defines program

    DispatcherParameterCountDemo()
      stdout <- Stdout()
      reporter <- AreaReporter()
      stdout.println(reporter.report(Circle()))
      stdout.println(reporter.report(Square()))

Java/Kotlin: overloaded methods may freely vary their parameter counts; the compiler picks an overload at compile time by static type, so arity differences are normal. EK9 dispatchers select a handler at run time by the argument's actual type, so all handlers must share the entry's arity - a difference is a compile-time error (E06320) rather than a silent overload.

E06320
arity
count
dispatch
dispatcher
entry
handler
invalid number of parameters
overload
parameter

dispatcher parameter count
E06320
dispatcher handler arity

report() as pure
        -> shape as Circle

report() as pure
        ->
          shape as Circle
          unit as String

Explanation: The dispatcher entry report takes one parameter, so every handler must also take exactly one parameter. A two-parameter handler cannot be aligned with the single-argument dispatch site, so the compiler reports E06320. Keep all handlers at the entry's arity (or make the entry two-parameter too). See ek9 -h E06320 for details.


Question: Why does my stream 'head' fail when I give it a Date variable instead of an Integer or a function?

What does E07870 (Integer or function required) mean for head/tail/skip?

Can I pass any variable as the count for head in an EK9 stream?

Why must the head/tail/skip count be an Integer value or a function?

Answer:
The count for a stream limiting stage (head, tail, skip) must be either an Integer value OR a zero-argument function/function-delegate. If you pass a plain variable that is neither an Integer nor a function (for example a Date), the compiler raises E07870 because it cannot derive an item count from that value.

VALID COUNTS
- Integer literal: cat items | head 2 > stdout
- Integer variable: cat items | head limit > stdout
- Function returning Integer: cat items | head howMany > stdout

Note a related but distinct check: if you DO pass a function but its return type is not Integer, you get E07550 (must return Integer) instead. E07870 is specifically the 'this is neither an Integer nor a function' case.

See Q812 for the full catalogue of stream pipeline errors.

defines module qa.streams.headcountfunction

  defines function

    //Valid count supplier: zero arguments, returns Integer.
    howMany()
      <- rtn as Integer: 2

  defines program

    StreamHeadCountDemo()
      stdout <- Stdout()

      items <- ["alpha", "beta", "gamma", "delta"]

      // === head with an Integer literal ===
      stdout.println("Head 2 (literal):")
      cat items | head 2 > stdout

      // === head with a function returning Integer ===
      stdout.println("Head howMany (function returns Integer):")
      cat items | head howMany > stdout

Java: Stream.limit(long) only accepts a primitive long, so there is no delegate form at all. Python: itertools.islice takes a plain int. Rust: .take(usize) takes an integer directly. EK9: head/tail/skip accept either an Integer value or a function/delegate that returns Integer, validated at compile time via E07870.

E07870 Integer count delegate function head skip stream tail

stream head count
E07870
Integer or function required

Explanation: head/tail/skip need an Integer value or a function/function-delegate. 'howMany' is a zero-argument function returning Integer so it is a valid count supplier, but 'dueDate' is a Date variable, which is neither an Integer nor a function, so the compiler raises E07870. (A function that returns a non-Integer would instead raise E07550.) See ek9 -h E07870 for details.


Question: Why does E07175 fire on my generic class with a 'T?' field and a default constructor?

My generic 'default Container() as pure' is rejected — why?

How do I stop a generic's optional field being null?

E07175 default constructor must be private when uninitialised properties exist — on a generic

Answer:
A generic field declared 'item as T?' is uninitialised at declaration. If the only no-argument constructor is the compiler-generated 'default Container()', that constructor leaves 'item' NULL — and any later use (for example the synthetic '?' operator reading 'item?') would dereference null. This is the billion-dollar mistake, so EK9 rejects it at compile time with E07175.

  Error : E07175: 'Container' ...: default constructor must be 'private' when uninitialised properties exist

THE IDIOMATIC FIX: give the field an unset T()
Replace 'default Container()' with a real no-arg constructor that first-initialises the field to an unset T(). The field is then present-but-unset (never null):

  Container of type T
    item as T?
    Container() as pure
      item :=? T()                 // present-but-unset, never null
    Container() as pure
      -> initial as T
      item :=? initial

WHY ':=?' (not ':=' or ':')
In a pure constructor ':=' is forbidden (E08100). The guarded ':=?' assigns only if the field is unset, which is exactly 'first-initialise to an unset value'.

IF T() IS NOT AVAILABLE
When T may be abstract, may hold a function, or has no public no-arg constructor, you cannot call T(). Then declare 'default private Container()' instead - external code cannot create an empty one, and callers must use the inferred constructor with a value.

See Q1347 for creating empty instances. See Q1348 for the T() limits. See Q645 for the private no-arg escape hatch.

defines module qa.genericsdeep.optionalfieldnullsafety

  defines class

    Container of type T
      item as T?

      Container() as pure
        item :=? T()

      Container() as pure
        -> initial as T
        item :=? initial

      hasItem() as pure
        <- rtn as Boolean: item?

      default operator ?

  defines program

    OptionalFieldDemo()
      stdout <- Stdout()

      emptyOne <- Container() of String
      stdout.println(`emptyOne hasItem: ${emptyOne.hasItem()}`)

      filled <- Container("hello")
      stdout.println(`filled hasItem: ${filled.hasItem()}`)

Java/Kotlin: a generic field T is null until assigned; reading it risks NullPointerException at runtime. Rust: Option<T> forces an explicit None. EK9: a 'T?' field must be made present-but-unset (item :=? T()) or the type must forbid empty construction (default private) - the null is eliminated at compile time, not deferred to runtime.

E07175 T? constructor default field generic guarded-assignment null optional present-but-unset uninitialised

Container() as pure
        item :=? T()

Explanation: A generic 'default Container()' leaves a 'T?' field null. Give the field an unset T() in the no-arg constructor (item :=? T()), or declare the default private. See ek9 -h E07175 for details.


Question: When must a generic class use 'default private' for its no-argument constructor?

I cannot call T() in my generic - now what?

My generic holds an abstract type or a function and item :=? T() fails

How do I write a generic with an optional field that cannot be defaulted?

Answer:
The idiomatic way to keep a generic's 'T?' field non-null is to give it an unset T() in the no-arg constructor ('item :=? T()'). But T() is not always available - it requires the concrete type used to parameterise the generic to have a public no-argument constructor. T() is NOT available when:
1. T may be ABSTRACT - an abstract type cannot be constructed (E10030).
2. T may hold a FUNCTION - a function cannot be constructed with T() (E06110).
3. T may have NO public no-arg constructor - for example a type whose own default is 'default private' (E10031).
4. T's no-arg constructor may be NON-pure while the generic calls 'T()' in a PURE constructor - a pure 'T()' demands a pure no-arg constructor on the parameterising type (E10032).

These constructor requirements (existence, public access, purity) are properties of the CONCRETE type, so they are only checked at parameterisation - not when the generic is defined.

In these cases, declare the no-arg default PRIVATE:

  Holder of type T
    item as T?
    default private Holder() as pure          // escape hatch - no T() needed
    Holder() as pure
      -> initial as T
      item :=? initial

WHAT 'default private' GIVES YOU
External code CANNOT create an empty Holder (the no-arg constructor is private), so it can never observe a null field. Callers must use the inferred constructor with a real value. The inferred (parameterised) constructor stays public so type inference still works.

DECISION RULE
- T always has a public no-arg constructor (String, Integer, your own value types): use 'item :=? T()' so empty construction is allowed and the field is present-but-unset.
- T might not (abstract bound, function, or no-arg-private types): use 'default private' and construct only with a value.

See Q1346 for the E07175 error. See Q1348 for the two safe shapes. See Q645 for the constructor access rule.

defines module qa.genericsdeep.defaultprivateescapehatch

  defines class

    Holder of type T
      item as T?

      default private Holder() as pure

      Holder() as pure
        -> initial as T
        item :=? initial

      get() as pure
        <- rtn as T: item

      default operator ?

  defines program

    EscapeHatchDemo()
      stdout <- Stdout()

      //Cannot do 'Holder() of String' here - the no-arg default is private.
      filled <- Holder("present")
      stdout.println(`filled set: ${filled?}`)

Java: a generic with an abstract or functional T simply leaves the field null and you hope no one reads it. EK9: you must choose - either guarantee an unset value with T(), or forbid empty construction with 'default private'. Either way the null is designed out at compile time.

E06110 E07175 E10030 E10031 E10032 T abstract constructor default escape-hatch function generic no-arg private pure

Explanation: A generic with an uninitialised 'T?' field must make its no-arg default constructor 'private' (E07175); a public 'default' no-arg constructor would let external code create an empty instance whose field is never set. Keep it 'default private' so callers must supply a value via the inferred constructor. See ek9 -h E07175 for details.


Question: What are the two safe ways to write a generic class that has an optional 'T?' field?

How should a generic optional field be initialised in EK9?

Default-value generic field versus no-empty-construction generic field

Generic class with a nullable field done safely

Answer:
A generic 'T?' field is uninitialised at declaration, so EK9 forces you into one of exactly two safe shapes - both eliminate the null at compile time.

SHAPE 1: PRESENT-BUT-UNSET (public no-arg, give the field a T())
Use when T is guaranteed to have a public no-argument constructor (String, Integer, your own value types). Empty construction is allowed; the field is an unset T, never null:

  Box of type T
    content as T?
    Box() as pure
      content :=? T()
    Box() as pure
      -> initial as T
      content :=? initial

  empty <- Box() of String     // allowed; content is unset, '?' is false

SHAPE 2: NO EMPTY CONSTRUCTION (default private)
Use when T() is not available (T may be abstract, hold a function, or lack a public no-arg constructor). The no-arg default is private, so no empty instance can be created; callers must pass a value:

  Box of type T
    content as T?
    default private Box() as pure
    Box() as pure
      -> initial as T
      content :=? initial

  filled <- Box(someValue)     // only way to construct - always has a value

WHAT IS NOT ALLOWED
A public 'default Box()' with a 'T?' field is E07175 - it would leave the field null. Pick shape 1 or shape 2.

The difference is a deliberate design choice: shape 1 says 'an empty one is meaningful (and unset)', shape 2 says 'there is no meaningful empty one'.

See Q1346 for the E07175 error. See Q1347 for the private escape hatch. See Q643 for the two-constructor requirement.

defines module qa.genericsdeep.twosafeshapes

  defines class

    //Shape 1: present-but-unset - an empty one is meaningful
    Box of type T
      content as T?

      Box() as pure
        content :=? T()

      Box() as pure
        -> initial as T
        content :=? initial

      default operator ?

    //Shape 2: no empty construction - there is no meaningful empty one
    Required of type T
      content as T?

      default private Required() as pure

      Required() as pure
        -> initial as T
        content :=? initial

      default operator ?

  defines program

    TwoSafeShapesDemo()
      stdout <- Stdout()

      emptyBox <- Box() of String
      stdout.println(`empty box set: ${emptyBox?}`)

      filledBox <- Box("value")
      stdout.println(`filled box set: ${filledBox?}`)

      onlyWithValue <- Required("must have")
      stdout.println(`required set: ${onlyWithValue?}`)

Java/Kotlin: nothing forces a choice - the generic field is null until set and the risk is silent. Rust: Option<T> is always explicit. EK9: the compiler forces shape 1 (present-but-unset via T()) or shape 2 (no empty construction via default private), so a generic optional field is never a hidden null.

T? default design field generic null-safety optional present-but-unset private shapes two

Explanation: A public default constructor with a 'T?' field leaves it null (E07175). Choose shape 1 (content :=? T()) or shape 2 (default private). See ek9 -h E07175 for details.


Question: I got E08250 THIS_ESCAPES_CONSTRUCTOR — why can't I pass 'this' out of a constructor?

Why does EK9 reject passing this in a constructor?

How to register an object during construction in EK9?

Publishing this from a constructor body EK9

Answer:
E08250 fires when a constructor passes `this` as an argument to another method or function before construction has finished. The object is only partially built at that point — its fields may still be unset — so letting a reference escape lets other code (or another thread) observe a half-initialised object. This is the classic 'unsafe publication' bug that causes intermittent NullPointer-style failures and data races.

WHAT THE ERROR MEANS
Inside a constructor body, `this` is still being assembled. Handing it to `reg.register(this)`, storing it in a collection owned by someone else, or capturing it into a function that outlives the constructor all let the not-yet-finished object be used. EK9 rejects any `this` argument inside a constructor body.

HOW TO FIX
Complete construction first, then register. Move the escaping call OUT of the constructor into a separate step the caller performs after the object exists, or use a factory function that constructs the object fully and only then registers it.

  publisher <- Publisher("news")   //fully constructed
  registry.register(publisher)      //register afterwards — safe

WHY EK9 DETECTS THIS
Unsafe publication is invisible in most languages — Java, C#, and C++ all let `this` escape a constructor with no warning, and the resulting races only surface under load. EK9 forbids it structurally so a reference to a half-built object can never be observed.

defines module qa.concurrency.this.escapes.constructor

  defines class

    Registry
      items as List of Publisher: List() of Publisher
      default Registry()
      register()
        -> p as Publisher
        items += p
      count() as pure
        <- rtn as Integer: length items
      default operator ?

    Publisher
      name as String: String()
      Publisher()
        ->
          n as String
          reg as Registry
        this.name: n
        stdout <- Stdout()
        stdout.println(`registry size ${reg.count()}`)
      default operator ?

Java/C#/C++: `this` may escape a constructor freely; escape analysis tools flag only some cases and the resulting races are load-dependent. EK9: any `this` argument inside a constructor body is a compile-time error, eliminating unsafe publication entirely.

E08250 constructor escapes half-built initialisation publication race register safe this unsafe

Explanation: The constructor passes `this` to `reg.register(this)` before the Publisher is fully built, letting a half-initialised object be observed (and, across threads, raced on). The fix is to finish construction and register afterwards from the caller, or via a factory. See ek9 -h E08250.


Question: I got E08251 SHARED_STATE_MUTATION_OUTSIDE_LOCK — why can't my async worker write shared state?

Why must I write shared state inside a lock in EK9?

async worker mutates shared field error E08251

How to mutate shared data safely across an async boundary

Answer:
E08251 fires when code mutates shared state that crosses a thread boundary without doing so inside a MutexLock.enter(...) body. The most common trigger is capturing a shared object into a `| async` worker and writing one of its fields there — the worker runs on a different thread, so the write races with anyone else touching that object.

WHAT THE ERROR MEANS
A `| async` stage dispatches each element to a thread-pool worker. If the worker captures a shared record and writes its field, that write happens off the caller's thread with no synchronisation. EK9 requires every mutation of shared state to happen inside a MutexKey body guarded by a MutexLock.

HOW TO FIX
Either keep the mutation on the same thread (read-only in the worker, aggregate afterwards), or route the mutation through a MutexLock so the write is serialised. Reading shared state in a worker is fine — only mutation is rejected.

  //worker only READS; the write happens under a lock elsewhere
  worker <- (shared) is asyncWorkerFn as function
    rtn: shared.total > 0

WHY EK9 DETECTS THIS
Data races on shared mutable state are among the hardest bugs to reproduce. EK9 makes the thread boundary visible (`| async`) and tracks which state the worker captures, refusing any unsynchronised mutation.

defines module qa.concurrency.shared.state.outside.lock

  defines record
    Tally
      total <- 0
      default operator ?

  defines function

    asyncWorkerFn() as abstract
      <- rtn as Boolean?

    toLine() as pure
      -> ok as Boolean
      <- rtn as String: $ok

    runPipeline()
      stdout <- Stdout()
      shared <- Tally()
      worker <- (shared) is asyncWorkerFn as function
        rtn: shared.total > 0
      workers <- [ worker ]
      cat workers | async | map with toLine > stdout

Java/Go: nothing stops a worker/goroutine from mutating captured shared state; races are found (if ever) via stress testing or the race detector at runtime. EK9: mutation of thread-crossing shared state outside a MutexLock is a compile-time error.

E08251 MutexLock async captured concurrent lock mutation race shared state worker

shared.total: shared.total + 1
        rtn: true

Explanation: The async worker captures the shared `Tally` and writes `shared.total` on a thread-pool thread with no lock, racing any other access. The safe form only reads in the worker; any mutation must go through a MutexLock body. See ek9 -h E08251.


Question: I got E08252 NESTED_ENTER_DIFFERENT_LOCK — how does nesting two locks form a deadlock cycle?

Why does nesting two MutexLocks fail in EK9?

two locks acquired in opposite order EK9

Answer:
E08252 fires when EK9's deadlock detector finds two code paths that nest the same pair of distinct locks in OPPOSITE orders — one acquires lockA then lockB, another acquires lockB then lockA. Two threads running these paths concurrently can each hold one lock and wait forever for the other.

WHAT THE ERROR MEANS
Every `lockA.enter(...)` with a `lockB.enter(...)` nested inside records an edge lockA -> lockB in a workspace-wide precedence graph. When updateA nests getB() inside getA() (edge A->B) and updateB nests getA() inside getB() (edge B->A), the graph has a cycle A->B->A — a deadlock.

HOW TO FIX
Never hold two locks at once when the acquisition order can contradict. Acquire them one at a time (release the first before taking the second), or unify the protected state under a single MutexLock of a record.

  require pair.getA().enter(keyA)   //completes and releases
  require pair.getB().enter(keyB)   //independent — no nesting

WHY EK9 DETECTS THIS
Lock-order cycles only deadlock under specific interleavings, so they slip through testing. EK9 builds the precedence graph at compile time (the model the Linux kernel's lockdep uses at runtime) and refuses to compile any cycle.

defines module qa.concurrency.nested.enter.different

  defines class

    LockPair
      lockA as MutexLock of Integer: MutexLock(Integer(0))
      lockB as MutexLock of Integer: MutexLock(Integer(0))
      getA()
        <- rtn as MutexLock of Integer: lockA
      getB()
        <- rtn as MutexLock of Integer: lockB
      default operator ?

  defines function

    updateA()
      -> pair as LockPair
      keyA <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require pair.getA().enter(keyA)

    updateB()
      -> pair as LockPair
      keyB <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require pair.getB().enter(keyB)

Java/C#: lock-order deadlocks are found at runtime via thread dumps; static tools catch only simple cases. Go: the runtime detector fires only on total deadlock. EK9: workspace-wide static cycle detection on the lock precedence graph, rejected at compile time.

E08252 MutexLock concurrent cycle deadlock enter getA getB lock nested order precedence

updateA()
      -> pair as LockPair
      innerA <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      outerA <- (pair, innerA) is MutexKey of Integer as function
        require pair.getB().enter(innerA)
      require pair.getA().enter(outerA)

    updateB()
      -> pair as LockPair
      innerB <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      outerB <- (pair, innerB) is MutexKey of Integer as function
        require pair.getA().enter(innerB)
      require pair.getB().enter(outerB)

Explanation: updateA nests getB() inside getA() (edge A->B) while updateB nests getA() inside getB() (edge B->A), forming a cycle A->B->A — a runtime deadlock. Fix by acquiring the locks sequentially (no nesting) or unifying them under one MutexLock of a record. See ek9 -h E08252.


Question: I got E08255 UNPROVABLE_LOCK_ORDER — why are two locks of the same type rejected even with no reverse nesting?

Why does EK9 reject nesting two same-type locks?

bank transfer from.getLock()/to.getLock() deadlock EK9

Answer:
E08255 fires when two locks of the SAME type — held on caller-supplied objects — are nested, even if only ONE nesting direction appears in the code. Because the two objects (`from` and `to` here) are interchangeable at the call site, a single static `from` then `to` ordering can instantiate at runtime as both `A,B` and `B,A`. The compiler cannot PROVE a consistent global order, so it rejects the nesting.

WHAT THE ERROR MEANS
Unlike E08252 (two distinct lock fields, provably orderable), here both locks are `theLock` reached through same-type parameters. `moveFunds(x, y)` and `moveFunds(y, x)` both type-check, so nesting `to` inside `from` is simultaneously both acquisition orders — the classic bank-transfer / dining-philosophers deadlock.

HOW TO FIX
Don't nest same-type locks. Acquire them one at a time, or put the shared payload under a SINGLE MutexLock of an owning record so only one lock is ever taken.

  require from.getLock().enter(fromKey)   //completes and releases
  require to.getLock().enter(toKey)        //separate, unnested

WHY EK9 DETECTS THIS
Same-type nested locks are the textbook deadlock (two accounts, two philosophers). Runtime ordering tricks (lock by hashcode) are error-prone; EK9 rejects the shape at compile time.

defines module qa.concurrency.unprovable.lock.order

  defines class

    Vault
      theLock as MutexLock of Integer: MutexLock(Integer(0))
      getLock() as pure
        <- rtn as MutexLock of Integer: theLock
      default operator ?

  defines function

    moveFunds()
      ->
        from as Vault
        to as Vault
      fromKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require from.getLock().enter(fromKey)

      toKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require to.getLock().enter(toKey)

Java: the canonical fix is to lock by System.identityHashCode ordering — easy to get wrong, invisible if wrong. EK9: nesting two same-type locks reached via interchangeable objects is a compile-time error (E08255); unify under one lock or acquire sequentially.

E08255 MutexLock concurrent deadlock instance lock nested order philosophers same transfer type unprovable

deadlock
unprovable order
same-type locks

innerKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      outerKey <- (to, innerKey) is MutexKey of Integer as function
        require to.getLock().enter(innerKey)
      require from.getLock().enter(outerKey)

Explanation: Both locks are `theLock` reached through same-type params `from`/`to`, which are interchangeable at the call site, so nesting `to` inside `from` instantiates as both orders at runtime — an unprovable order. Fix by acquiring sequentially or unifying under one MutexLock of a record. See ek9 -h E08255.


Question: How do I extend a base whose no-argument constructor is private (E07176 / E07177)?

Why does my sub-type get E07176 'constructor cannot call a private super constructor'?

E07177 the super type's no-argument constructor is private — what do I do?

My 'default Sub()' is rejected because the base has 'default private Base()'

A sub-class with no constructor won't compile when the super default is private

Answer:
A base whose properties are uninitialised must make its no-arg default 'private' (E07175). That private default is NOT accessible to a sub-type, and EVERY constructor chains to a super constructor - explicitly (super(...) / this(...)) or via a hidden IMPLICIT super() to the super's no-arg. So a sub-type cannot reach the private base default.

WHAT HAPPENS
1. A sub-type that WRITES a no-arg default (or any constructor with no explicit super(...)) implicitly calls the private base default -> E07176.
2. A sub-type with NO constructor at all: EK9 would normally synthesise a public no-arg default, but its hidden super() cannot reach the private base default, so none is synthesised - the type is unconstructable -> E07177.

THE FIX: call an accessible initialising super(...)
  Shape as abstract
    name as String?
    default private Shape() as pure
    Shape() as pure
      -> n as String
      name :=? String(n)

  Circle is Shape
    Circle() as pure
      super("circle")          // reaches the accessible, initialising Shape(String)

EK9 does not mandate a no-argument constructor - the sub-type simply has none, and is constructed via the constructor that calls super(...).

See Q886 for E07175 (private default). See Q581 for super() delegation. See Q1346 for the 'T?' null-safety idiom.

defines module qa.constructordelegation.privatesuperconstructor

  defines class

    Shape as abstract
      name as String?

      //Uninitialised property -> the no-arg default must be private (E07175). That private default is not
      //reachable by any sub-type, so sub-types must chain to the accessible initialising Shape(String).
      default private Shape() as pure

      Shape() as pure
        -> n as String
        name :=? String(n)

      name() as pure
        <- rtn as String: String(name)

      default operator ?

    //Correct: Circle calls an accessible, initialising super(...). It has no synthesised no-arg default
    //(EK9 does not mandate one) - it is constructed via this constructor.
    Circle is Shape
      Circle() as pure
        super("circle")

  defines program

    PrivateSuperDemo()
      stdout <- Stdout()

      c <- Circle()
      stdout.println(c.name())

Java/Kotlin: a subclass constructor implicitly calls super() and fails to compile if the base no-arg constructor is inaccessible ('there is no default constructor available'). EK9 reports the same class of problem precisely: E07176 when a sub-type constructor chains (implicitly or explicitly) to a private super, and E07177 when no constructor exists at all and no default can be synthesised.

CONSTRUCTOR_REQUIRED_FOR_PRIVATE_SUPER CONSTRUCTOR_USES_PRIVATE_SUPER E07176 E07177 abstract constructor default delegation implicit inheritance private sub-type super uninitialised

Circle() as pure
        super("circle")

Explanation: A written 'default Circle()' (or any constructor without an explicit super(...)) implicitly calls the private Shape() - not accessible. Call an accessible initialising super(...) instead. See ek9 -h E07176.

Explanation: A sub-type with NO constructor cannot have a public default synthesised (its implicit super() could not reach the private Shape()). Define a constructor that calls an accessible super(...). See ek9 -h E07177.


Question: How do I use a guard in the expression form of a while loop?

Can a while loop that returns a value also use a guard variable?

How do ?= and :=? guards work in a while expression?

while loop as an expression with a guard operator

How do I combine a guarded assignment with a while loop that yields a result?

Answer:
The while loop has an expression form (it yields a value via a returning variable) and that form accepts the same guards as every other control-flow construct. The guard is written in the header, BEFORE the loop control, separated by 'then' (or 'with').

WHILE EXPRESSION WITH A ?= GUARD
The guarded assignment (?=) assigns the right-hand side to the guard variable and checks whether it is SET. If unset, the whole loop body is skipped and the expression yields the returning variable's initial value:
  reading as Integer?
  looping <- true
  total <- while reading ?= nextReading() then looping
    <- rtn as Integer: 0
    rtn: rtn + reading
    looping: false
  // total is 0 if nextReading() was unset, otherwise the accumulated value

WHILE EXPRESSION WITH A :=? GUARD
Assign-if-unset (:=?) only assigns when the guard variable is currently unset; it still gates the body on the resulting set-state:
  cached as Integer?
  loopOnce <- true
  lazy <- while cached :=? nextReading() then loopOnce
    <- rtn as Integer: -1
    rtn: cached
    loopOnce: false

HEADER ORDER MATTERS
The guard comes FIRST, then the control condition after 'then' (or 'with'):
  while <guard> then <control>
Not 'while <control> then <guard>'. The guard is evaluated once at entry; if it leaves the guard variable unset the body never runs.

RETURNING VARIABLE
The returning variable ('<- rtn as T: initial') is declared as the first line of the body and MUST have an initial value, so a skipped body still yields a defined result. This is why a guard-unset while expression returns the initialiser.

See Q77 for while guards in statement form. See Q81 for the while expression form without a guard. See Q1361 for the same guard used uniformly across all six expression-form constructs. See Q74 for the three guard operators.

defines module qa.controlflow.guardwhileexpression

  defines function

    nextReading()
      <- rtn as Integer: 42

  defines program

    GuardWhileExpressionDemo()
      stdout <- Stdout()

      // while EXPRESSION with a ?= guarded-assignment guard
      reading as Integer?
      looping <- true
      total <- while reading ?= nextReading() then looping
        <- rtn as Integer: 0
        rtn: rtn + reading
        looping: false
      stdout.println(`accumulated total is ${total}`)

      // while EXPRESSION with a :=? assign-if-unset guard
      cached as Integer?
      loopOnce <- true
      lazy <- while cached :=? nextReading() then loopOnce
        <- rtn as Integer: -1
        rtn: cached
        loopOnce: false
      stdout.println(`lazy value is ${lazy}`)

Java: no equivalent. A while loop is a statement, never an expression, and cannot declare a loop-scoped guarded variable. You would write a nullable variable, a manual isSet check, and a mutable accumulator across three or more statements. Kotlin: while is a statement; you would use a sequence/fold to get a value. Rust: 'while let Some(x) = iter.next()' binds and loops but is still a statement, not a value-producing expression. EK9: the while expression yields a value AND accepts a guard (<-, ?=, :=?) in one header, with the returning variable guaranteeing a defined result even when the guard is unset.

:=? ?= assignment control expression flow guard guarded isset loop returning then value while

while expression
guard variable
guarded assignment

total <- while looping then reading ?= nextReading()
        <- rtn as Integer: 0
        rtn: rtn + reading

Explanation: The guard is the pre-flow part and must come FIRST in the header; the loop control comes after 'then' (or 'with'). Writing the control first and the guard second is not valid. See ek9 -h E02001.