Question: How does EK9 validate the number of type parameters for generics?

What is E06020 incorrect number of parameters for generic?

What happens if I provide wrong number of type arguments?

How does EK9 check generic parameter counts?

Answer:
EK9 validates that the number of type arguments matches the number of type parameters declared on a generic type. Providing too few or too many type arguments produces E06020.

SINGLE PARAMETER TYPES
Types declared 'of type T' need exactly one type argument:
  List of String         // correct: 1 param, 1 arg
  Optional of Integer    // correct: 1 param, 1 arg

MULTI PARAMETER TYPES
Types declared 'of type (K, V)' need exactly two:
  Dict of (String, Integer)  // correct: 2 params, 2 args

MISMATCH DETECTED
E06020 is raised when counts do not match:
  Dict of String          // ERROR: needs 2, got 1
  List of (String, Integer)  // ERROR: needs 1, got 2

PARAMETER SUPPLY
Always match the generic declaration:
  MyType of type T         -> supply 1 type
  MyType of type (A, B)    -> supply 2 types
  MyType of type (X, Y, Z) -> supply 3 types

See Q196 for multi-parameter generics. See Q198 for built-in generics. See Q642 for constructor inference.

defines module qa.genericsdeep.parametercount

  defines class

    <?-
      Single parameter generic: needs exactly 1 type argument.
    -?>
    Wrapper of type T
      item as T?

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

      Wrapper() as pure
        -> inputItem as T
        item :=? inputItem

      default operator ?

    <?-
      Two parameter generic: needs exactly 2 type arguments.
    -?>
    Association of type (K, V)
      theKey as K?
      theValue as V?

      Association() as pure
        theKey :=? K()
        theValue :=? V()

      Association() as pure
        ->
          k as K
          v as V
        theKey :=? k
        theValue :=? v

      default operator ?

  defines program

    ParameterCountDemo()
      stdout <- Stdout()

      // === SINGLE PARAM: one type argument ===

      intWrap <- Wrapper(42)
      stdout.println(`Int wrapper: ${intWrap?}`)

      strWrap <- Wrapper() of String
      stdout.println(`String wrapper: ${strWrap?}`)

      // === TWO PARAMS: two type arguments ===

      assoc <- Association("key", 100)
      stdout.println(`Association: ${assoc?}`)

      emptyAssoc <- Association() of (String, Float)
      stdout.println(`Empty association: ${emptyAssoc?}`)

      // === BUILT-IN GENERICS FOLLOW SAME RULES ===

      names <- List() of String
      names += "Alice"
      stdout.println(`List count: ${length names}`)

      mapping <- Dict() of (String, Integer)
      stdout.println(`Dict set: ${mapping?}`)

Java: compiler checks type argument count against declaration. C++: template parameter count validated. Rust: generic parameter count validated. Go: type parameter count validated (Go 1.18+). Kotlin: same as Java. EK9: E06020 raised for parameter count mismatch, checked early in compilation.

E06020 argument count generic mismatch parameter type type-parameter validation

Explanation: The number of type arguments must match the number of type parameters declared on the generic. Association declares two parameters (K, V), so providing only one triggers E06020. See ek9 -h E06020 for details.


Question: Can I overload methods with different parameterised types in EK9?

Does EK9 allow method overloading with generics unlike Java?

How do independent generic types enable overloading?

Answer:
Because EK9 creates independent types for each parameterisation (no erasure), you can overload methods with different parameterised types. This is impossible in Java due to type erasure.

OVERLOADING WORKS
Because List of Integer and List of String are distinct types:
  process(items as List of Integer)
  process(items as List of String)
Both signatures coexist without ambiguity.

WHY JAVA CANNOT
Java erases generics at runtime: both List<Integer> and List<String> become List. Two methods with the same erased signature conflict.

AMBIGUITY DETECTION (E06140)
EK9 still detects GENUINE ambiguity. If two overloads match equally well, E06140 is raised. This happens with coercion costs, not with distinct parameterised types.

DISTINCT TYPE ARGUMENTS
Overloading with different type arguments is always unambiguous:
  handler(opt as Optional of String)
  handler(opt as Optional of Integer)
The compiler resolves the correct overload from the argument type.

See Q653 for type independence. See Q196 for multi-parameter generics. See Q194 for generic class basics.

defines module qa.genericsdeep.methodoverloading

  defines function

    <?-
      Functions accepting different parameterised types.
      Each List parameterisation is an independent type,
      so these are unambiguous overloads (unlike Java with erasure).
    -?>
    sumIntegers()
      -> items as List of Integer
      <- rtn as Integer: 0
      for item in items
        rtn += item

    joinStrings()
      -> items as List of String
      <- rtn as String: ""
      for item in items
        rtn += item

    countIntegers() as pure
      -> items as List of Integer
      <- rtn as Integer: length items

    countStrings() as pure
      -> items as List of String
      <- rtn as Integer: length items

  defines program

    MethodOverloadingDemo()
      stdout <- Stdout()

      // === INTEGER LIST ===

      numbers <- List() of Integer
      numbers += 10
      numbers += 20
      numbers += 30
      intSum <- sumIntegers(numbers)
      stdout.println(`Integer sum: ${intSum}`)

      // === STRING LIST ===

      words <- List() of String
      words += "hello"
      words += " "
      words += "world"
      joined <- joinStrings(words)
      stdout.println(`Joined: ${joined}`)

      // === COUNT FUNCTIONS FOR DIFFERENT LIST TYPES ===

      intCount <- countIntegers(numbers)
      stdout.println(`Integer count: ${intCount}`)

      strCount <- countStrings(words)
      stdout.println(`String count: ${strCount}`)

Java: cannot overload methods with different generic parameterisations (erasure). C++: can overload with different template instantiations. Rust: no method overloading (uses traits instead). Go: no method overloading. Kotlin: same erasure limitation as Java. EK9: full overloading with distinct parameterised types because no erasure.

E06140 ambiguity distinct erasure generic method migrate overload parameterised type-parameter

sumIntegers()
      -> items as List of Integer
      <- rtn as Integer: 0
    sumIntegers()
      -> items as List of Integer
      <- rtn as Integer: 0

Explanation: E01030 is raised when two overloads match equally well, creating genuine ambiguity. Declaring two functions with identical signatures (same name, same parameter types) would be ambiguous. EK9 allows overloading with different parameterised types because they are independent. See ek9 -h E01030 for details.


Question: How does EK9 validate argument counts when calling generic functions?

What are E06280 and E06290 too many or too few arguments?

How does EK9 check function call argument counts?

What happens if I pass wrong number of arguments to a generic function?

Answer:
EK9 validates that the number of arguments passed to a function or method matches the declared parameter count. Too many arguments raises E06280, too few raises E06290.

EXACT MATCH REQUIRED
A function declared with two parameters:
  combine(a as String, b as String)
Must be called with exactly two arguments:
  combine("hello", "world")  // correct

TOO MANY (E06280)
Passing more arguments than declared:
  combine("a", "b", "c")  // ERROR: expects 2, got 3

TOO FEW (E06290)
Passing fewer arguments than declared:
  combine("a")  // ERROR: expects 2, got 1

GENERIC FUNCTION CALLS
Same validation applies to generic function calls:
  transform(source as S)  // needs exactly 1 argument
  transform(x, y)         // ERROR: too many

NO OPTIONAL PARAMETERS
EK9 has no default parameter values (by design). Every declared parameter must receive an argument.

See Q597 for no default parameters design. See Q642 for constructor inference. See Q654 for type parameter count.

defines module qa.genericsdeep.argumentcount

  defines function

    <?-
      Single parameter function. Requires exactly 1 argument.
    -?>
    square() as pure
      -> number as Integer
      <- rtn as Integer: number * number

    <?-
      Two parameter function. Requires exactly 2 arguments.
    -?>
    multiply() as pure
      ->
        factor1 as Integer
        factor2 as Integer
      <- rtn as Integer: factor1 * factor2

    <?-
      Generic single parameter function.
    -?>
    echo() of type T as open
      -> item as T
      <- rtn as T: T(item)

  defines program

    ArgumentCountDemo()
      stdout <- Stdout()

      // === SINGLE ARG: exactly 1 ===

      sq <- square(7)
      stdout.println(`Square of 7: ${sq}`)

      // === TWO ARGS: exactly 2 ===

      product <- multiply(factor1: 6, factor2: 7)
      stdout.println(`Product: ${product}`)

      // === GENERIC SINGLE ARG ===

      echoed <- echo(42)
      stdout.println(`Echoed: ${echoed}`)

      strEchoed <- echo("hello")
      stdout.println(`Str echoed: ${strEchoed}`)

Java: exact argument count required (no defaults for methods). C++: default parameters allow fewer args. Rust: exact count required (no defaults). Go: exact count required (no defaults or varargs for regular funcs). Kotlin: default parameters allow fewer. Python: default params and *args. EK9: exact count always, no defaults by design (E06280/E06290).

E06280
E06290
argument
count
function
generic
too few
too many
type-parameter
validation

Explanation: square() declares exactly one parameter, so passing two arguments triggers E06270 (too many arguments). See ek9 -h E06270 for details.

Explanation: multiply() declares two parameters (factor1 and factor2), so passing only one argument triggers E06270 (too few arguments). See ek9 -h E06270 for details.


Question: How does service URI mapping work with :/path syntax?

How do I define URI paths for EK9 services?

What is the :/path syntax in service definitions?

How do service URIs bind to methods and operators?

Answer:
EK9 services use ':/path' syntax for URI mapping. The service declaration includes a base path, and each method or operator adds a relative path.

BASE PATH
Declare a service with a root URI:
  Items :/items
This binds the service to the '/items' path prefix.

METHOD PATHS
Named methods declare additional path segments:
  welcome() as GET for :/welcome
This creates a GET endpoint at '/items/welcome'.

OPERATOR PATHS WITH PARAMETERS
Operators use path parameters:
  operator -= :/{itemId}
This creates a DELETE endpoint at '/items/{itemId}'.

PATH PARAMETER BINDING
Path parameters bind to method parameters:
  -> itemId as String
The server extracts the value from the URL and passes it.

See Q199 for GET endpoints. See Q200 for CRUD operators. See Q658 for path parameter binding. See Q202 for parameter binding.
See Q684 for service URI paths. See Q685 for service method bodies.

defines module qa.webdeep.urimapping

  defines service

    <?-
      Service with base URI and multiple endpoint paths.
      Each method and operator adds a relative path.
    -?>
    Catalogue :/catalogue open

      // GET /catalogue/all — list items
      listAll() as GET for :/all
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `["itemA", "itemB"]`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "max-age=60"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      // GET /catalogue/{itemCode} — get single item
      byCode() as GET for :/{itemCode}
        -> itemCode as String
        <- response as HTTPResponse: (capturedCode: itemCode) with trait HTTPResponse
          override content()
            <- rtn as String: `{"code": "${capturedCode}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "max-age=30"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    CatalogueApp
      register Catalogue()

  defines program

    ServiceUriMappingDemo()
      stdout <- Stdout()

      stdout.println("Service URI mapping:")
      stdout.println("  Base: /catalogue")
      stdout.println("  GET /catalogue/all -> listAll()")
      stdout.println("  GET /catalogue/{itemCode} -> byCode()")

Java: @RequestMapping('/items') on class, @GetMapping('/welcome') on method. Python: @app.route('/items/welcome'). Go: http.HandleFunc('/items/welcome', handler). Rust: web::resource('/items').route(web::get().to(handler)). EK9: 'Items :/items' on service, 'method() as GET for :/welcome' on method.

E07790 HTTP REST dict endpoint http mapping path route service uri

Explanation: The function getMapping is not defined in this module. Use string literals or defined functions. See ek9 -h E50001 for details.


Question: How do I extract path parameters in EK9 services?

How does PATH binding work in EK9 services?

How do I get URL path variables in service methods?

What is E07880 service parameter binding error?

Answer:
EK9 services extract path parameters using :/{paramName} syntax. The path variable binds to a method parameter of the same name.

PATH PARAMETER
Declare a path variable in the URI:
  byId() as GET for :/{orderId}
    -> orderId as String
The server extracts orderId from the URL path.

CAPTURE PATTERN FOR DYNAMIC RESPONSE
Path parameters are NOT in scope inside the dynamic HTTPResponse class body. To use a path parameter in response content, capture it in the dynamic class expression:
  <- response as HTTPResponse: (capturedId: orderId) with trait HTTPResponse
    override content()
      <- rtn as String: `{"id": "${capturedId}"}`
The captured variable is accessible within the dynamic class methods.

CONTENT BINDING
Use :=: CONTENT for request body:
  operator += :/
    -> body as String :=: CONTENT
The request body is bound to the 'body' parameter.

HTTPRESPONSE RETURN
Every service method must return HTTPResponse:
  <- response as HTTPResponse: ...

See Q657 for URI mapping basics. See Q200 for CRUD operators. See Q201 for HTTP responses. See Q659 for HTTPResponse requirements. See Q660 for CRUD operators.

defines module qa.webdeep.pathbinding

  defines service

    <?-
      Service demonstrating path parameter extraction
      and content body binding.
    -?>
    Orders :/orders open

      // GET /orders/{orderId} — extract path param
      byId() as GET for :/{orderId}
        -> orderId as String
        <- response as HTTPResponse: (capturedId: orderId) with trait HTTPResponse
          override content()
            <- rtn as String: `{"orderId": "${capturedId}"}`
          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 ?

      // POST /orders — content body binding
      operator += :/
        -> orderJson as String :=: CONTENT
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"received": true}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    OrderApp
      register Orders()

  defines program

    PathParameterDemo()
      stdout <- Stdout()

      stdout.println("Path parameter binding:")
      stdout.println("  GET /orders/{orderId} -> byId(orderId)")
      stdout.println("  POST /orders with CONTENT -> operator +=")

Java: @PathVariable('orderId') String orderId. Python: Flask route('<orderId>'). Go: mux.Vars(r)['orderId']. Rust: web::Path<String>. EK9: ':/{orderId}' in URI, '-> orderId as String' in parameter list.

E07880 REST URL binding extract http parameter path service variable

Explanation: Path parameters in the URI must have a matching method parameter with the same name. If the URI declares {orderId}, the method must have a parameter named orderId. See ek9 -h E07700 for details.


Question: Why must every service method return HTTPResponse?

What return type do EK9 service methods require?

How do I construct an HTTPResponse in a service?

Answer:
Every service method and operator must return HTTPResponse. The response encapsulates the HTTP status code, content body, content type, cache control, and language.

HTTPRESPONSE TRAIT
The HTTPResponse trait defines the contract:
  status() — HTTP status code (200, 201, 404, etc.)
  content() — response body as String
  contentType() — MIME type (application/json, text/html, etc.)
  cacheControl() — caching directives
  contentLanguage() — response language

DYNAMIC CLASS PATTERN
Create responses with anonymous dynamic classes:
  <- response as HTTPResponse: () with trait HTTPResponse
    override content()
      <- rtn as String: "response body"
    override status() as pure
      <- rtn as Integer: 200
    ...

WHY REQUIRED
The web server infrastructure needs standardised response data. Without HTTPResponse, the server cannot send status codes, headers, or content back to clients.

See Q201 for HTTP response details. See Q657 for URI mapping. See Q200 for CRUD operators.

defines module qa.webdeep.returntype

  defines service

    <?-
      Service with methods that correctly return HTTPResponse.
      Every method must satisfy the HTTPResponse trait contract.
    -?>
    Health :/health open

      // GET /health/status — returns 200 with JSON body
      healthCheck() as GET for :/status
        <- 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 ?

      // GET /health/version — returns version info
      version() as GET for :/version
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"version": "1.0.0"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "max-age=3600"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    HealthApp
      register Health()

  defines program

    ServiceReturnTypeDemo()
      stdout <- Stdout()

      stdout.println("All service methods return HTTPResponse:")
      stdout.println("  status() -> HTTP status code")
      stdout.println("  content() -> response body")
      stdout.println("  contentType() -> MIME type")
      stdout.println("  cacheControl() -> caching rules")

Java: Spring ResponseEntity<T> or JAX-RS Response. Python: Flask return (body, status_code). Go: w.WriteHeader(status); w.Write(body). Rust: HttpResponse::Ok().json(). EK9: HTTPResponse trait with status(), content(), contentType(), cacheControl(), contentLanguage().

E07800 HTTPResponse content function http required return service status trait

Explanation: The function getReturnInfo is not defined in this module. See ek9 -h E50001 for details.


Question: How do CRUD operators map to URIs with path parameters?

How do I use += for POST and -= for DELETE in services?

What is the operator to HTTP verb mapping in EK9?

How do service operators handle path parameters?

Answer:
EK9 maps service operators to HTTP verbs with semantic meaning. Each operator has a natural CRUD mapping.

OPERATOR MAPPING
  operator += :/           POST (create resource)
  operator -= :/{id}       DELETE (remove resource)
  operator :~: :/{id}      PATCH (partial update)
  operator :^: :/{id}      PUT (full replacement)

PATH PARAMETERS IN OPERATORS
Operators can include path parameters:
  operator -= :/{resourceId}
    -> resourceId as String
    <- response as HTTPResponse: ...

CONTENT BINDING IN OPERATORS
POST and PUT operators accept request body:
  operator += :/
    -> body as String :=: CONTENT

SEMANTIC ALIGNMENT
The operator meanings align with HTTP:
  += adds to collection -> POST creates
  -= removes from collection -> DELETE removes
  :~: merges partially -> PATCH updates fields
  :^: replaces entirely -> PUT replaces

See Q200 for CRUD overview. See Q657 for URI mapping. See Q658 for path parameters.

defines module qa.webdeep.crudoperators

  defines service

    <?-
      Full CRUD service using operator-to-verb mapping.
      Each operator handles a different HTTP method.
    -?>
    Products :/products open

      // GET /products — list
      listProducts() :/
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `["widget", "gadget"]`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "max-age=30"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      // POST /products — create
      operator += :/
        -> productJson as String :=: CONTENT
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"created": true}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

      // DELETE /products/{productId} — remove
      operator -= :/{productId}
        -> productId as String
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: ""
          override status() as pure
            <- rtn as Integer: 204
          override contentType() as pure
            <- rtn as String: "text/plain"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    ProductApp
      register Products()

  defines program

    CrudOperatorDemo()
      stdout <- Stdout()

      stdout.println("CRUD operators in service:")
      stdout.println("  += maps to POST /products")
      stdout.println("  -= maps to DELETE /products/{id}")

Java: Spring @PostMapping, @DeleteMapping, @PatchMapping, @PutMapping. Python: Flask methods=['POST']. Go: manual method switch. Rust: actix .post()/.delete(). EK9: operators += (POST), -= (DELETE), :~: (PATCH), :^: (PUT) with URI paths.

CRUD DELETE E07860 PATCH POST PUT dict http mapping operator service

Explanation: The function getCrudInfo is not defined in this module. See ek9 -h E50001 for details.

Explanation: The function getPostInfo is not defined in this module. See ek9 -h E50001 for details.


Question: Why can sanitized only be used on incoming String parameters?

What is E07920 sanitized in wrong location?

Can I mark fields or return values as sanitized?

Answer:
The 'sanitized' modifier can ONLY be applied to INCOMING String parameters. It works by injecting call-site sanitization code that creates defensive copies.

WHY ONLY STRING
Sanitization prevents injection attacks (SQL, XSS, command injection). These attacks exploit string interpolation into commands or markup. Non-string types like Integer or Boolean don't have this attack vector (E07910).

WHY ONLY INCOMING PARAMETERS
Sanitized works by injecting code at the CALL SITE. Fields have no call site (they're initialized directly). Return parameters flow OUT, not IN. Local variables are internal with no external data entry point (E07920).

CORRECT PATTERN
  processInput()
    -> userInput as sanitized String
The caller's string is copied and sanitized before the function body runs.

CONSTRUCTOR PATTERN
To sanitize data stored in fields, sanitize the constructor parameter:
  SafeRecord()
    -> input as sanitized String
    name :=? input

See Q215 for sanitized basics. See Q217 for sanitized with purity. See Q662 for safe copy patterns.

defines module qa.sanitizeddeep.paramonly

  defines function

    <?-
      Sanitized on incoming String parameter.
      The compiler injects call-site sanitization code
      that creates a defensive copy before the body runs.
    -?>
    processUserInput()
      -> userInput as sanitized String
      <- result as String: ""

      safeCopy <- String(userInput)
      result: safeCopy

    <?-
      Sanitized in constructor parameter.
      Fields get sanitized data through constructor injection.
    -?>
    buildGreeting()
      -> userName as sanitized String
      <- greeting as String: ""

      greeting: "Hello, " + userName

  defines class

    <?-
      Class that accepts sanitized input through constructor.
      The field stores already-sanitized data.
    -?>
    SafeRecord
      storedName as String: String()

      SafeRecord()
        -> inputName as sanitized String
        storedName :=? inputName

      getName() as pure
        <- rtn as String: storedName

      default operator ?

  defines program

    SanitizedParameterDemo()
      stdout <- Stdout()

      cleaned <- processUserInput("safe text")
      stdout.println(`Processed: ${cleaned}`)

      message <- buildGreeting("Alice")
      stdout.println(message)

      record <- SafeRecord("Bob")
      stdout.println(`Stored: ${record.getName()}`)

Java: manual InputValidation.sanitize(input). Python: bleach.clean(). Go: html.EscapeString(). Rust: ammonia::clean(). EK9: 'sanitized' modifier on String parameters with automatic call-site injection.

E07910 E07920 String call-site injection parameter sanitize sanitized security validate

Explanation: The sanitized modifier can only be applied to String parameters. Non-string types like Integer have no injection attack vector. See ek9 -h E07910 for details.

Explanation: String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.


Question: How do I safely work with sanitized parameters without aliasing?

What is E07930 assignment from sanitized?

Why can't I assign a sanitized parameter to a local variable?

How do I copy a sanitized value in pure and non-pure contexts?

Answer:
Direct assignment FROM a sanitized parameter creates hidden aliasing (E07930). Both variables point to the SAME sanitized copy, so mutations to one affect the other.

HIDDEN ALIASING
  someFunc(input as sanitized String)
    alias <- input       // BOTH point to same memory!
Both 'alias' and 'input' share the same defensive copy.

PURE CONTEXT: COPY CONSTRUCTOR ONLY
  pureProcess() as pure
    -> input as sanitized String
    localCopy <- String(input)    // Creates NEW independent copy

NON-PURE CONTEXT: MORE OPTIONS
  nonPureProcess()
    -> input as sanitized String
    safeCopy <- String(input)     // Option 1: copy constructor
    target: ""
    target :=: safeCopy           // Option 2: copy operator on initialized var

DIRECT EXPRESSION USE
  directUse() as pure
    -> input as sanitized String
    result: "Output: " + input    // OK: concatenation creates new value

The copy constructor pattern works in ALL contexts and is the recommended approach.

See Q661 for sanitized parameter basics. See Q215 for sanitized overview. See Q663 for override matching.

defines module qa.sanitizeddeep.safecopy

  defines function

    <?-
      Pure context: copy constructor is the ONLY safe option.
      Direct assignment would create hidden alias to sanitized copy.
    -?>
    pureExtract() as pure
      -> rawInput as sanitized String
      <- cleaned as String: ""

      safeCopy <- String(rawInput)
      cleaned: safeCopy

    <?-
      Direct expression use works without assignment.
      String concatenation creates a new value, not an alias.
    -?>
    formatMessage() as pure
      -> userMessage as sanitized String
      <- formatted as String: ""

      formatted: "Message: " + userMessage

    <?-
      Non-pure context: copy constructor works here too.
      This is the universal safe pattern.
    -?>
    logAndProcess()
      -> inputData as sanitized String
      <- processed as String: ""

      localCopy <- String(inputData)
      processed: localCopy

    <?-
      Passing sanitized parameter to another function is always safe.
      The receiving function gets the sanitized copy, no alias created.
    -?>
    delegateWork()
      -> externalInput as sanitized String
      <- result as String: ""

      result: formatMessage(externalInput)

  defines program

    SafeCopyDemo()
      stdout <- Stdout()

      cleanResult <- pureExtract("user data")
      stdout.println(`Pure extract: ${cleanResult}`)

      formatted <- formatMessage("hello world")
      stdout.println(formatted)

      logged <- logAndProcess("log entry")
      stdout.println(`Logged: ${logged}`)

      delegated <- delegateWork("delegated input")
      stdout.println(delegated)

Java: manual String copying with new String(). Python: copy.copy() for defensive copies. Go: strings are immutable (value semantics). Rust: clone(). EK9: String(input) copy constructor prevents aliasing from sanitized parameters.

E07930 String aliasing assignment copy defensive immutable pure sanitize sanitized security side-effect validate

Explanation: Direct assignment from a sanitized parameter creates hidden aliasing where both variables point to the same sanitized copy. Use the copy constructor String(param) to create an independent copy. See ek9 -h E07930 for details.


Question: Why must sanitized match exactly in overridden methods?

What is E07940 sanitized mismatch in override?

Can I add sanitized to an override method?

What happens if I remove sanitized from an overriding method?

Answer:
The 'sanitized' modifier must MATCH between a method and the method it overrides (E07940). The Liskov Substitution Principle requires consistent security behavior through polymorphic dispatch.

WHY MATCH IS REQUIRED
Consider polymorphic dispatch:
  handler <- getHandler()      // Could be Base or Derived
  handler.process(userInput)   // Is it sanitized or not?
If sanitized doesn't match, security depends on runtime type.

TWO FAILURE CASES
Case 1: Super has sanitized, override removes it
  -> Caller expects sanitization but override skips it = vulnerability
Case 2: Super has no sanitized, override adds it
  -> Caller doesn't expect data modification = LSP violation

CORRECT PATTERN
Both must agree:
  defines class Base as open
    process()
      -> input as sanitized String
  defines class Derived extends Base
    override process()
      -> input as sanitized String    // Must match!

TRAIT NOTE
Traits cannot have 'default operator ?' (E07030). When defining a trait with sanitized methods, only classes implementing the trait can have 'default operator ?'.

See Q661 for sanitized basics. See Q662 for safe copy patterns. See Q664 for increment expression restriction. See Q215 for sanitized overview.

defines module qa.sanitizeddeep.overridematch

  defines trait

    <?-
      Trait defining sanitized contract.
      All implementations must preserve sanitized on the parameter.
    -?>
    InputProcessor
      process() as abstract
        -> inputText as sanitized String
        <- result as String?

  defines class

    <?-
      Base class with sanitized parameter.
      Subclasses MUST keep sanitized to maintain LSP.
    -?>
    BaseHandler as open
      handleRequest()
        -> requestBody as sanitized String
        <- response as String: ""

        safeCopy <- String(requestBody)
        response: safeCopy

      default operator ?

    <?-
      Correct override: sanitized matches the parent.
      Security behavior is consistent through polymorphism.
    -?>
    DerivedHandler extends BaseHandler
      override handleRequest()
        -> requestBody as sanitized String
        <- response as String: ""

        safeCopy <- String(requestBody)
        response: "Derived: " + safeCopy

      default operator ?

    <?-
      Trait implementation with matching sanitized.
    -?>
    SafeProcessor with trait of InputProcessor
      override process()
        -> inputText as sanitized String
        <- result as String: ""

        localCopy <- String(inputText)
        result: "Processed: " + localCopy

      default operator ?

  defines program

    OverrideMatchDemo()
      stdout <- Stdout()

      baseHandler <- BaseHandler()
      baseResponse <- baseHandler.handleRequest("base input")
      stdout.println(baseResponse)

      derivedHandler <- DerivedHandler()
      derivedResponse <- derivedHandler.handleRequest("derived input")
      stdout.println(derivedResponse)

      processor <- SafeProcessor()
      processResult <- processor.process("trait input")
      stdout.println(processResult)

Java: no enforcement, override can change validation. Python: no enforcement. Go: no inheritance. Rust: trait implementations must match exactly. EK9: compiler enforces sanitized matching in overrides for LSP compliance.

E07940 LSP Liskov abstract function handler match open override polymorphic sanitize sanitized sealed security validate virtual visitor

baseResponse <- baseHandler.handleRequest("base input").toUpperCase()


Question: Why are ++ and -- statement-only operators in EK9?

What is E07950 increment in expression context?

Answer:
In EK9, '++' and '--' are STATEMENT-ONLY operators (E07950). They cannot be used in expressions where a value is expected.

WHY REMOVED FROM EXPRESSIONS
In C/Java, 'y <- x++' has confusing semantics:
  C: y gets the OLD value before increment (surprising!)
  Java: y becomes an ALIAS to x (both reference same object!)
Both are sources of subtle bugs.

CORRECT PATTERN
  x++                    // Statement only: increments x
  y <- x                 // Assign separately if needed
  // Or use explicit arithmetic:
  y <- x + 1             // Clear intent, predictable

STATEMENT VS EXPRESSION
Statement context (allowed):
  counter++              // Standalone increment
  counter--              // Standalone decrement
Expression context (not allowed):
  result <- counter++    // ERROR: no value returned
  list += counter--      // ERROR: no value returned

See Q50 for loop alternatives. See Q283 for loop patterns without break.

defines module qa.sanitizeddeep.incrementstatement

  defines program

    IncrementStatementDemo()
      stdout <- Stdout()

      // === CORRECT: Statement-only increment ===

      counter <- 0

      counter++
      stdout.println(`After increment: ${counter}`)

      counter++
      stdout.println(`After second increment: ${counter}`)

      counter--
      stdout.println(`After decrement: ${counter}`)

      // === CORRECT: Explicit arithmetic for expressions ===

      baseValue <- 10
      nextValue <- baseValue + 1
      stdout.println(`Next value: ${nextValue}`)

      previousValue <- baseValue - 1
      stdout.println(`Previous value: ${previousValue}`)

      // === CORRECT: Loop with separate increment ===

      loopCount <- 0
      iterations <- 5
      while loopCount < iterations
        stdout.println(`Iteration ${loopCount}`)
        loopCount++

Java: x++ returns old value, ++x returns new value (confusing). C++: same semantics, major bug source. Python: no ++ operator. Go: ++ is statement-only (same as EK9). Rust: no ++ operator. EK9: ++ and -- are statement-only, use explicit arithmetic for expressions.

E07950 decrement expression increment migrate mutation operator sanitize security statement validate

Explanation: The ++ and -- operators are statement-only in EK9. They cannot be used in expression context where a value is expected. Use explicit arithmetic like 'nextValue <- counter + 1' instead. See ek9 -h E50050 for details.


Question: How do sanitized parameters work with web service content binding?

Can I use sanitized with CONTENT binding in services?

How do I sanitize request bodies in EK9 services?

How does sanitized interact with service operators?

Answer:
Sanitized parameters combine naturally with service content binding. When a service operator receives request body content via :=: CONTENT, the parameter can be marked sanitized for automatic input cleaning.

SANITIZED CONTENT BINDING
  operator += :/
    -> body as sanitized String :=: CONTENT
The request body is both bound from HTTP content AND sanitized before the operator body runs.

SANITIZED PATH PARAMETERS
  byId() as GET for :/{resourceId}
    -> resourceId as sanitized String
Path parameters from URLs can also be sanitized.

PATTERN: SAFE API
Combine sanitized with copy constructor in service methods:
  operator += :/
    -> payload as sanitized String :=: CONTENT
    safeCopy <- String(payload)
    // Process safeCopy safely

See Q661 for sanitized basics. See Q662 for copy patterns. See Q657 for URI mapping. See Q658 for path parameters.

defines module qa.sanitizeddeep.serviceintegration

  defines function

    <?-
      Standalone function demonstrating sanitized parameter
      in a function context, similar to how service methods work.
    -?>
    processPayload()
      -> payload as sanitized String
      <- result as String: ""

      safeCopy <- String(payload)
      result: "Received: " + safeCopy

    <?-
      Function that accepts sanitized input and delegates.
    -?>
    handleRequest()
      -> requestBody as sanitized String
      <- response as String: ""

      response: processPayload(requestBody)

  defines service

    <?-
      Service with sanitized content binding on POST.
      The request body is sanitized before the operator body runs.
    -?>
    SecureApi :/api open

      // GET /api/status — no sanitization needed for read
      status() :/status
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"status": "ok"}`
          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 ?

      // POST /api — sanitized content binding
      operator += :/
        -> payload as sanitized String :=: CONTENT
        <- response as HTTPResponse: () with trait HTTPResponse
          override content()
            <- rtn as String: `{"accepted": true}`
          override status() as pure
            <- rtn as Integer: 201
          override contentType() as pure
            <- rtn as String: "application/json"
          override cacheControl() as pure
            <- rtn as String: "no-store"
          override contentLanguage() as pure
            <- rtn as String: "en"
          default operator ?

  defines application

    SecureApiApp
      register SecureApi()

  defines program

    SanitizedServiceDemo()
      stdout <- Stdout()

      stdout.println("Sanitized service integration:")
      stdout.println("  POST /api with sanitized CONTENT binding")
      stdout.println("  Automatic injection prevention on request body")

      processed <- processPayload("safe user data")
      stdout.println(processed)

Java: Spring @RequestBody with manual validation. Python: Flask request.get_json() with schema validation. Go: manual sanitization after body read. Rust: actix extractors with validation. EK9: sanitized modifier on CONTENT-bound parameter provides automatic call-site sanitization.

CONTENT E07910 E07930 binding sanitize sanitized security service validate web

-> payload as sanitized Integer :=: CONTENT

Explanation: The sanitized modifier can only be applied to String parameters. Injection attacks exploit string interpolation, so non-string types have no attack vector. See ek9 -h E07910 for details.

Explanation: Direct assignment from a sanitized parameter creates hidden aliasing. Use the copy constructor String(param) to create an independent copy that avoids shared mutable state. See ek9 -h E07930 for details.


Question: Why can't I inject components in a pure context?

What is E08140 injection in pure context?

Why does injection conflict with purity?

How do I use components in pure methods?

Answer:
Component injection is an impure operation because it involves runtime state management (E08140). The DI container resolves and provides instances at runtime, which conflicts with purity guarantees.

WHY INJECTION IS IMPURE
Pure functions guarantee no side effects and deterministic results. Injection accesses mutable singleton state managed by the DI container, making the result non-deterministic.

CORRECT PATTERN
Inject at the field level, then pass data to pure methods:
  defines component
    MyComponent
      repository as Repository!

      processData() as pure
        -> items as List of String
        <- count as Integer: length items

The impure injection happens at field level. Pure methods receive data through parameters.

See Q111 for component basics. See Q560 for purity contracts. See Q667 for abstract injection.

defines module qa.divalidation.purecontext

  defines component

    <?-
      Abstract component defining repository contract.
    -?>
    DataStore as abstract

      fetchAll() as abstract
        <- rtn as List of String?

      default operator ?

    <?-
      Concrete implementation of the data store.
    -?>
    InMemoryStore extends DataStore

      override fetchAll()
        <- rtn <- List() of String
        rtn += "record-alpha"
        rtn += "record-beta"

      default operator ?

  defines function

    <?-
      Pure function receives data as parameter,
      not through injection. This is the correct pattern
      for processing data without DI coupling.
    -?>
    countRecords() as pure
      -> records as List of String
      <- total as Integer: length records

  defines application

    DataApp
      register InMemoryStore() as DataStore

  defines program

    InjectionPureContextDemo() with application of DataApp
      stdout <- Stdout()

      store as DataStore!

      allRecords <- store.fetchAll()
      stdout.println(`Records fetched: ${length allRecords}`)

      recordCount <- countRecords(allRecords)
      stdout.println(`Count via pure function: ${recordCount}`)

Java: Spring allows injection everywhere (no purity concept). Python: no DI purity enforcement. Go: no DI framework. Rust: no runtime DI. EK9: injection is impure by design, use parameter passing for pure methods.

DI E08140 component context immutable impure inject injection pure registration side-effect text validate

      override fetchAll()
        <- rtn <- List() of String
        rtn += "record-alpha"
        rtn += "record-beta"

      pureButBroken() as pure
        injected as DataStore!
        require injected?

Explanation: Component injection with '!' is an impure operation (accesses DI container state). A pure method cannot contain injection points. Pass the component as a parameter instead. See ek9 -h E08140 for details.


Question: Why must injected components be abstract types?

What is E08150 injecting concrete component?

Why can't I inject a concrete component class?

How does abstract injection enable testing?

Answer:
EK9 requires injected component types to be abstract (E08150). This enforces the Dependency Inversion Principle: depend on abstractions, not implementations.

WHY ABSTRACT ONLY
Concrete injection creates tight coupling. Abstract injection allows:
  1. Different applications wire different implementations
  2. Test applications inject mocks
  3. Production applications inject real services

REGISTRATION MUST USE 'AS'
Concrete components must be registered with 'as AbstractType':
  register ConsoleLogger() as Logger    // Correct
  register ConsoleLogger()              // ERROR: E50020
Without 'as', the registration genus is incompatible.

INJECTION MUST USE ABSTRACT TYPE
  logger as Logger!                     // Correct: abstract
  logger as ConsoleLogger!              // ERROR: E08150

PATTERN
  defines component
    Logger as abstract
      log() as abstract
        -> message as String
    ConsoleLogger extends Logger
      override log()
        -> message as String
        stdout <- Stdout()
        stdout.println(message)

  defines application
    ProdApp
      register ConsoleLogger() as Logger

  defines program
    MyProgram with application of ProdApp
      logger as Logger!       // Inject abstract

See Q111 for component basics. See Q666 for pure context. See Q668 for injectable contexts. See Q324 for Autowired equivalent. See Q325 for Component equivalent.

defines module qa.divalidation.abstractonly

  defines component

    <?-
      Abstract logger contract.
      Concrete implementations are registered in applications.
    -?>
    Logger as abstract

      logMessage() as abstract
        -> logEntry as String

      default operator ?

    <?-
      Console logger implementation.
    -?>
    ConsoleLogger extends Logger

      override logMessage()
        -> logEntry as String
        stdout <- Stdout()
        stdout.println(logEntry)

      default operator ?

    <?-
      Abstract repository contract.
    -?>
    UserRepository as abstract

      findByName() as abstract
        -> userName as String
        <- result as String?

      default operator ?

    <?-
      In-memory repository implementation.
    -?>
    InMemoryUserRepo extends UserRepository

      override findByName()
        -> userName as String
        <- result as String: ""

        result: "Found: " + userName

      default operator ?

  defines application

    ProductionApp
      register ConsoleLogger() as Logger
      register InMemoryUserRepo() as UserRepository

  defines program

    AbstractInjectionDemo() with application of ProductionApp
      stdout <- Stdout()

      logger as Logger!
      userRepo as UserRepository!

      logger.logMessage("Starting application")

      foundUser <- userRepo.findByName("Alice")
      stdout.println(foundUser)

      logger.logMessage("Application complete")

Java: Spring injects concrete types (bad practice). Python: duck typing, no enforcement. Go: interfaces encouraged but not required. Rust: trait objects for abstraction. EK9: compiler enforces abstract injection, concrete types cannot be injected.

DI E08150 abstract component concrete inject injection interface registration validate

Explanation: Only abstract component types can be injected. Injecting a concrete type like ConsoleLogger creates tight coupling and defeats the purpose of dependency injection. Depend on the abstract type Logger and let the application wiring choose the implementation. See ek9 -h E08150 for details.


Question: Which constructs support component injection?

What is E08160 injection not possible in context?

Answer:
Only specific EK9 constructs support component injection with the '!' suffix (E08160). Injection is available in components, services, and programs linked to applications.

INJECTABLE CONTEXTS
  defines component — field injection
  defines service — field injection
  defines program — with application of AppName

NON-INJECTABLE CONTEXTS
  defines function — no DI container access
  defines record — no DI container access
  defines class — no DI container access (use component instead)

PROGRAM REQUIREMENT
Programs must declare 'with application of AppName' to enable injection. Without this linkage, the compiler cannot resolve which implementations to inject (E08220).

See Q111 for component basics. See Q666 for pure context. See Q667 for abstract injection. See Q672 for program-application linking.

defines module qa.divalidation.injectablecontexts

  defines component

    <?-
      Abstract notification service contract.
    -?>
    NotificationService as abstract

      sendNotification() as abstract
        -> recipient as String

      default operator ?

    <?-
      Email-based notification implementation.
    -?>
    EmailNotifier extends NotificationService

      override sendNotification()
        -> recipient as String
        stdout <- Stdout()
        stdout.println(`Email sent to ${recipient}`)

      default operator ?

  defines application

    OrderApp
      register EmailNotifier() as NotificationService

  defines program

    <?-
      Program with injection: 'with application of' enables DI.
      This is the correct injectable context for programs.
    -?>
    InjectableContextDemo() with application of OrderApp
      stdout <- Stdout()

      notifier as NotificationService!
      notifier.sendNotification("demo-recipient")

      stdout.println("Injection works in programs linked to applications")
      stdout.println("Components and services also support injection")

Java: Spring @Autowired works in any managed bean. Python: inject anywhere with frameworks. Go: manual wiring. Rust: no DI framework. EK9: injection limited to components, services, and programs for architectural clarity.

DI E08160 component constant context inject injection program registration service validate

NonInjectableDemo()
      notifier as NotificationService!

Explanation: Component injection with '!' is only valid in components, services, and programs linked to an application. Functions, records, and classes do not have access to the DI container. Programs must use 'with application of' to enable injection. See ek9 -h E50001 for details.


Question: Why can't I reassign an injected component variable?

What is E08170 reassignment of injected variable?

Can I change an injected dependency at runtime?

Answer:
Injected dependencies are set by the DI container and cannot be directly reassigned (E08170). The container manages the lifecycle, and manual reassignment would break the contract between application wiring and injection sites.

WHY NO REASSIGNMENT
The application definition declares which concrete component satisfies each abstract injection point. If code reassigns the variable, the wiring becomes unpredictable.

GUARDED ASSIGNMENT ALLOWED
Use ':=?' for conditional fallback:
  service as MyService!
  service :=? fallbackService    // Only assign if not injected

This respects the container's decision: if injection succeeded, the guard does nothing.

See Q667 for abstract injection. See Q668 for injectable contexts. See Q670 for field initialization.

defines module qa.divalidation.reassignment

  defines component

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

      lookup() as abstract
        -> cacheKey as String
        <- cachedValue as String?

      default operator ?

    <?-
      Simple in-memory cache implementation.
    -?>
    InMemoryCache extends CacheService

      override lookup()
        -> cacheKey as String
        <- cachedValue as String: ""

        cachedValue: "cached:" + cacheKey

      default operator ?

  defines application

    CacheApp
      register InMemoryCache() as CacheService

  defines program

    InjectionReassignmentDemo() with application of CacheApp
      stdout <- Stdout()

      cache as CacheService!
      directLookup <- cache.lookup("settings")
      stdout.println(`Lookup: ${directLookup}`)

      anotherLookup <- cache.lookup("user-profile")
      stdout.println(`Another: ${anotherLookup}`)

Java: Spring injection is final by convention (not enforced). Python: no enforcement. Go: manual wiring can be changed. Rust: ownership prevents aliasing. EK9: compiler prevents reassignment of injected variables, guarded assignment allowed.

DI E08170 component guarded immutable inject injection isset null-safe reassignment registration safe validate

      cache as CacheService!
      cache := InMemoryCache()
      directLookup <- cache.lookup("settings")

Explanation: Injected component variables are set by the DI container and cannot be reassigned with ':='. The application wiring defines which implementation satisfies each injection point, and manual reassignment would break that contract. See ek9 -h E08170 for details.


Question: Why must component fields be initialized or injected?

What is E08180 property not initialized?

How do I initialize fields in components?

What is the difference between field initialization and injection?

Answer:
Every component field must either have an initial value or be marked for injection with '!' (E08180). Uninitialized, non-injected fields leave the object in an undefined state.

FIELD INITIALIZATION
  defines component
    MyComponent
      counter as Integer: 0             // Initialized
      items as List of String: List() of String  // Initialized

FIELD INJECTION
  defines component
    MyComponent
      repository as Repository!          // Injected by container

BOTH IN SAME COMPONENT
  defines component
    OrderService
      repository as Repository!          // Injected
      retryLimit as Integer: 3           // Initialized

See Q667 for abstract injection. See Q669 for reassignment. See Q671 for circular dependencies.

defines module qa.divalidation.fieldinit

  defines component

    <?-
      Abstract message formatter contract.
    -?>
    MessageFormatter as abstract

      formatText() as abstract
        -> rawText as String
        <- formatted as String?

      default operator ?

    <?-
      Uppercase formatter implementation.
    -?>
    UpperCaseFormatter extends MessageFormatter

      override formatText()
        -> rawText as String
        <- formatted as String: ""

        formatted: rawText.upperCase()

      default operator ?

  defines application

    NotificationApp
      register UpperCaseFormatter() as MessageFormatter

  defines program

    FieldInitDemo() with application of NotificationApp
      stdout <- Stdout()

      formatter as MessageFormatter!
      result <- formatter.formatText("hello world")
      stdout.println(`Formatted: ${result}`)

Java: fields can be null (NPE risk). Python: fields set in __init__. Go: zero values for all fields. Rust: all fields must be initialized. EK9: fields must be initialized or injected, no uninitialized state allowed.

DI E08180 component field initialization inject injection property registration validate

Explanation: Every component field must either be initialized with a value or marked for injection with '!'. A field declared without initialization and without the injection suffix leaves the object in an undefined state. Use '!' for DI-managed fields or provide an initial value. See ek9 -h E08180 for details.


Question: How does EK9 prevent circular dependencies in DI?

What is E08190 circular dependency detected?

How do I break a dependency cycle between components?

What happens when two components inject each other?

Answer:
EK9 detects circular dependencies at compile time (E08190). When Component A injects Component B, and Component B injects Component A (directly or transitively), the compiler rejects the code.

WHY CIRCULAR IS FORBIDDEN
Circular dependencies make object creation impossible. To create A, you need B. To create B, you need A. This deadlock is detected before runtime.

BREAKING CYCLES
1. Introduce an intermediary:
   A -> C <- B (C mediates between A and B)
2. Use event-based communication
3. Restructure to remove the cycle

CORRECT HIERARCHICAL PATTERN
  Repository -> (no dependencies)
  Service -> Repository
  Controller -> Service
Dependencies flow in ONE direction.

See Q667 for abstract injection. See Q670 for field initialization. See Q673 for missing registration.

defines module qa.divalidation.circulardep

  defines component

    <?-
      Abstract storage contract.
      Bottom of the dependency hierarchy (no injections).
    -?>
    Storage as abstract

      store() as abstract
        -> payload as String
        <- stored as Boolean?

      default operator ?

    <?-
      Concrete storage implementation.
    -?>
    FileStorage extends Storage

      override store()
        -> payload as String
        <- stored as Boolean: true

      default operator ?

    <?-
      Abstract processing contract.
      Depends on Storage (one direction).
    -?>
    Processor as abstract

      processItem() as abstract
        -> itemData as String
        <- result as String?

      default operator ?

    <?-
      Concrete processor that depends on storage.
      Dependencies flow: Processor -> Storage (no cycle).
    -?>
    ItemProcessor extends Processor

      storage as Storage!

      override processItem()
        -> itemData as String
        <- result as String: ""

        storedOk <- storage.store(itemData)
        if storedOk?
          result: "Processed: " + itemData

      default operator ?

  defines application

    HierarchicalApp
      register FileStorage() as Storage
      register ItemProcessor() as Processor

  defines program

    CircularDependencyDemo() with application of HierarchicalApp
      stdout <- Stdout()

      processor as Processor!
      result <- processor.processItem("test-data")
      stdout.println(result)

      stdout.println("Dependencies flow one direction: Processor -> Storage")
      stdout.println("No circular dependency possible")

Java: Spring detects circular dependencies at runtime (throws exception). Python: no detection. Go: manual wiring avoids cycles. Rust: ownership prevents cycles. EK9: compiler detects cycles at compile time, before any code runs.

DI E08190 circular component cycle dependency inject registration validate validation

    FileStorage extends Storage

      processor as Processor!

      override store()
        -> payload as String
        <- stored as Boolean: true

      default operator ?

Explanation: Adding an injection of Processor into FileStorage creates a circular dependency: ItemProcessor depends on Storage (via FileStorage), and FileStorage now depends on Processor. Neither can be constructed first. See ek9 -h E08190 for details.


Question: Why must programs declare 'with application of' for injection?

What is E08220 program without application?

How do I link a program to an application?

What happens if I inject without an application?

Answer:
Programs that use component injection must declare 'with application of AppName' (E08220). Without this linkage, the compiler cannot resolve which concrete implementations to inject.

LINKAGE SYNTAX
  defines program
    MyProgram() with application of MyApp
      service as AbstractService!    // Resolved from MyApp

WHY REQUIRED
The application definition is the wiring blueprint. It maps abstract types to concrete implementations. Without it, the compiler sees an injection point but has no way to resolve it.

MULTIPLE APPLICATIONS
Different programs can link to different applications:
  ProdProgram() with application of ProductionApp
  TestProgram() with application of TestApp
Same abstract types, different implementations.

See Q668 for injectable contexts. See Q673 for missing registration. See Q674 for duplicate registration.

defines module qa.divalidation.programlink

  defines component

    <?-
      Abstract audit service contract.
    -?>
    AuditService as abstract

      recordEvent() as abstract
        -> eventName as String

      default operator ?

    <?-
      Console-based audit implementation.
    -?>
    ConsoleAudit extends AuditService

      override recordEvent()
        -> eventName as String
        stdout <- Stdout()
        stdout.println(`Audit: ${eventName}`)

      default operator ?

  defines application

    <?-
      Application wiring for production.
      Maps abstract types to concrete implementations.
    -?>
    ProductionSetup
      register ConsoleAudit() as AuditService

  defines program

    <?-
      Program linked to application for injection.
      The 'with application of' clause enables injection resolution.
    -?>
    ProgramApplicationDemo() with application of ProductionSetup
      stdout <- Stdout()

      auditor as AuditService!
      auditor.recordEvent("application-started")

      stdout.println("Program linked to ProductionSetup application")
      stdout.println("Injection resolved through application wiring")

      auditor.recordEvent("demo-complete")

Java: Spring ApplicationContext is implicit (classpath scanning). Python: Flask/Django auto-discover. Go: manual wiring in main(). Rust: manual wiring. EK9: explicit 'with application of' declaration links program to its DI configuration.

DI E08220 application define inject injection linkage program registration validate wiring

Explanation: Programs that use component injection must declare 'with application of AppName' to link to a DI configuration. Without this linkage, the compiler has no application wiring blueprint to resolve which concrete implementations to inject. See ek9 -h E08220 for details.


Question: What happens when a component is injected but not registered?

What is E08210 missing registration for injected component?

How do I fix unresolved injection errors?

Why does my program fail with missing component registration?

Answer:
When a program injects a component type that has no matching registration in the linked application, the compiler raises E08210. Every injection point must have a corresponding 'register' declaration.

MISSING REGISTRATION
If program injects 'service as Logger!' but the application has no 'register ... as Logger', the compiler detects this at compile time.

COMPLETENESS VALIDATION
The compiler validates that ALL injection points in the program (and its transitive component dependencies) can be satisfied by the application's registrations.

FIXING
1. Add the missing registration to the application
2. Register a concrete implementation 'as' the abstract type
3. Ensure ALL transitive dependencies are registered

TRANSITIVE DEPENDENCIES
If ComponentA injects ComponentB, and ComponentB injects ComponentC, then the application must register implementations for BOTH B and C.

See Q672 for program-application linking. See Q674 for duplicate registration. See Q671 for circular dependencies.

defines module qa.divalidation.missingreg

  defines component

    <?-
      Abstract authentication service.
    -?>
    AuthService as abstract

      authenticate() as abstract
        -> credentials as String
        <- authenticated as Boolean?

      default operator ?

    <?-
      Token-based authentication implementation.
    -?>
    TokenAuth extends AuthService

      override authenticate()
        -> credentials as String
        <- authenticated as Boolean: true

      default operator ?

    <?-
      Abstract session manager.
    -?>
    SessionManager as abstract

      createSession() as abstract
        -> userName as String
        <- sessionToken as String?

      default operator ?

    <?-
      In-memory session manager implementation.
    -?>
    InMemorySessionManager extends SessionManager

      override createSession()
        -> userName as String
        <- sessionToken as String: ""

        sessionToken: "session-" + userName

      default operator ?

  defines application

    <?-
      Complete application: registers BOTH components.
      Every injection point in the program must be satisfied.
    -?>
    CompleteApp
      register TokenAuth() as AuthService
      register InMemorySessionManager() as SessionManager

  defines program

    MissingRegistrationDemo() with application of CompleteApp
      stdout <- Stdout()

      authService as AuthService!
      sessionMgr as SessionManager!

      isValid <- authService.authenticate("admin:secret")
      stdout.println(`Authenticated: ${isValid}`)

      token <- sessionMgr.createSession("admin")
      stdout.println(`Session: ${token}`)

Java: Spring NoSuchBeanDefinitionException at runtime. Python: runtime ImportError. Go: compile-time (manual wiring). Rust: compile-time (no DI). EK9: compile-time E08210 validates all injection points have registrations.

DI E08210 application compile-time component inject injection missing registration validate

IncompleteApp
      register TokenAuth() as AuthService

Explanation: Every injection point in the program and its transitive dependencies must have a matching registration in the linked application. If the program injects SessionManager but the application only registers AuthService, the compiler cannot resolve the SessionManager injection point. See ek9 -h E50010 for details.


Question: Why can't I register two implementations for the same abstract type?

How do I handle multiple implementations of one interface?

Can I register the same abstract type twice?

Answer:
Each abstract component type can have exactly ONE concrete registration per application (E08230). Duplicate registrations create ambiguity in injection resolution.

WHY ONE REGISTRATION ONLY
When a program injects 'service as Logger!', the compiler must resolve exactly one concrete implementation. Two registrations for Logger would make resolution ambiguous.

DIFFERENT APPLICATIONS FOR DIFFERENT WIRING
Instead of duplicate registrations, use different applications:
  defines application
    ProdApp
      register FileLogger() as Logger
  defines application
    TestApp
      register MockLogger() as Logger
Each application has one Logger binding.

See Q672 for program-application linking. See Q673 for missing registration. See Q671 for circular dependencies.

defines module qa.divalidation.duplicatereg

  defines component

    <?-
      Abstract email service contract.
    -?>
    EmailService as abstract

      sendEmail() as abstract
        -> recipient as String

      default operator ?

    <?-
      SMTP implementation for production.
    -?>
    SmtpEmailService extends EmailService

      override sendEmail()
        -> recipient as String
        stdout <- Stdout()
        stdout.println(`SMTP email to ${recipient}`)

      default operator ?

    <?-
      Mock implementation for testing.
    -?>
    MockEmailService extends EmailService

      override sendEmail()
        -> recipient as String
        stdout <- Stdout()
        stdout.println(`Mock email to ${recipient}`)

      default operator ?

  defines application

    <?-
      Production application: ONE registration for EmailService.
      Each abstract type has exactly one concrete binding.
    -?>
    ProductionConfig
      register SmtpEmailService() as EmailService

    <?-
      Test application: different implementation, same abstract type.
      Use separate applications for different wiring configurations.
    -?>
    TestConfig
      register MockEmailService() as EmailService

  defines program

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

      emailService as EmailService!
      emailService.sendEmail("user@example.com")

      stdout.println("One abstract type = one registration per application")
      stdout.println("Use different applications for different implementations")

Java: Spring requires @Primary or @Qualifier for multiple beans. Python: manual selection. Go: manual wiring. Rust: no DI framework. EK9: one registration per abstract type per application, use different applications for different configurations.

DI E08230 ambiguous application component duplicate inject registration validate

BrokenConfig
      register SmtpEmailService() as EmailService
      register MockEmailService() as EmailService

Explanation: Each abstract component type can have exactly one concrete registration per application. Registering two implementations for EmailService in the same application creates ambiguity when the compiler tries to resolve injection points. Use separate applications for different wiring configurations. See ek9 -h E50010 for details.


Question: How does EK9 validate transitive component dependencies?

Does EK9 check nested injection chains at compile time?

What if a component's dependency has unregistered dependencies?

Answer:
EK9 validates the ENTIRE dependency graph at compile time. If Program injects ComponentA, and ComponentA injects ComponentB, then BOTH must have registrations in the application.

TRANSITIVE VALIDATION
The compiler walks the full injection chain:
  Program -> ServiceA -> RepositoryB -> (no more deps)
All three levels must have registered implementations.

FAILURE AT ANY LEVEL
If RepositoryB has no registration, the compiler reports E08210 even though the program only directly injects ServiceA. The transitive chain is validated.

COMPLETE WIRING
  defines application
    FullApp
      register ConcreteRepoB() as RepositoryB
      register ConcreteServiceA() as ServiceA
Both registrations satisfy the transitive chain.

See Q673 for missing registration. See Q671 for circular dependencies. See Q666 for pure context.

defines module qa.divalidation.transitive

  defines component

    <?-
      Layer 1: Abstract data access.
      Bottom of the dependency chain.
    -?>
    DataAccess as abstract

      readRecord() as abstract
        -> recordKey as String
        <- recordData as String?

      default operator ?

    <?-
      Concrete data access implementation.
    -?>
    DatabaseAccess extends DataAccess

      override readRecord()
        -> recordKey as String
        <- recordData as String: ""

        recordData: "db:" + recordKey

      default operator ?

    <?-
      Layer 2: Abstract business logic.
      Depends on DataAccess (injection).
    -?>
    BusinessLogic as abstract

      executeRule() as abstract
        -> ruleInput as String
        <- ruleOutput as String?

      default operator ?

    <?-
      Concrete business logic.
      Injects DataAccess (transitive dependency).
    -?>
    OrderLogic extends BusinessLogic

      dataAccess as DataAccess!

      override executeRule()
        -> ruleInput as String
        <- ruleOutput as String: ""

        rawData <- dataAccess.readRecord(ruleInput)
        if rawData?
          ruleOutput: "Processed: " + rawData

      default operator ?

  defines application

    <?-
      Application must register ALL levels of the dependency chain.
      Missing any level triggers E08210.
    -?>
    TransitiveApp
      register DatabaseAccess() as DataAccess
      register OrderLogic() as BusinessLogic

  defines program

    TransitiveDiDemo() with application of TransitiveApp
      stdout <- Stdout()

      logic as BusinessLogic!
      result <- logic.executeRule("order-123")
      stdout.println(result)

      stdout.println("Transitive chain validated at compile time:")
      stdout.println("  Program -> BusinessLogic -> DataAccess")

Java: Spring validates at startup (runtime). Python: runtime discovery. Go: compile-time (manual). Rust: compile-time (no DI). EK9: full compile-time validation of transitive dependency chains.

DI E08210 chain compile-time component dependency inject registration transitive validate validation

IncompleteApp
      register OrderLogic() as BusinessLogic

Explanation: The compiler validates the entire dependency graph. OrderLogic injects DataAccess, so even though the program only directly injects BusinessLogic, the application must also register a DataAccess implementation. Missing any level in the transitive chain triggers this error. See ek9 -h E50010 for details.


Question: How do override and access modifiers interact in a three-level class hierarchy?

What is E05120 method signature mismatch in override?

What is E05130 access modifier mismatch in override?

What is E05140 covariant return type required in override?

How does override protected work in deep class hierarchies?

Answer:
When overriding in multi-level class hierarchies, three rules must hold at every level: the override keyword must be present, the access modifier must be the same or wider, and the signature must match exactly. These rules apply equally to METHODS and OPERATORS.

OVERRIDE ON METHODS AND OPERATORS
The override keyword works identically on both:
  override describe() as pure         method override
  override operator ? as pure         operator override
  override operator $ as pure         operator override
If the parent defines it, the child must say override.

OVERRIDE KEYWORD REQUIRED (E05120)
Any method or operator that replaces a parent version MUST use 'override'. Without it, the compiler reports E05120. This applies to operator ? (inherited from base type), operator $ (if parent defines it), and all other operators.

ACCESS MODIFIER MATCHING (E05130)
An override can maintain or WIDEN access. Narrowing access (public to private) violates Liskov and triggers E05130.

SIGNATURE MATCHING (E05140)
Parameter types and return types must match.

THREE-LEVEL CHAIN
Grandparent declares the contract. Parent overrides it. Grandchild overrides it again. Each level must independently satisfy override, access, and signature rules — for both methods and operators.

See Q570 for override basics. See Q575 for deep override chains. See Q579 for trait override chains.

defines module qa.override.accesshierarchy

  defines class

    <?-
      Level 1: Grandparent declares protected methods.
      These form the contract for all descendants.
    -?>
    Grandparent as abstract

      protected describe()
        <- rtn as String: "grandparent"

      protected classify() as pure abstract
        <- rtn as String?

      default operator ?

    <?-
      Level 2: Parent overrides with matching access.
      Protected stays protected, override keyword present.
    -?>
    Parent extends Grandparent as open

      override protected describe()
        <- rtn as String: "parent"

      override protected classify() as pure
        <- rtn as String: "parent-class"

      default operator ?

    <?-
      Level 3: Child overrides again, maintaining contract.
      Each level independently satisfies all three rules.
    -?>
    Child extends Parent

      override protected describe()
        <- rtn as String: "child"

      override protected classify() as pure
        <- rtn as String: "child-class"

      default operator ?

  defines program

    OverrideAccessHierarchyDemo()
      stdout <- Stdout()

      child <- Child()
      stdout.println(`Override chain verified: ${child?}`)

Java: @Override annotation, access can widen (private to protected), signatures must match. Kotlin: 'override' keyword mandatory, access widening allowed. Rust: no inheritance, trait impl must match. Python: no override keyword, duck typing. EK9: 'override' keyword mandatory, access must match or widen, signature must match exactly.

E05120 E05130 E05140 abstract access chain class hierarchy inherit open override protected signature virtual

Explanation: Without the 'override' keyword, the compiler treats 'protected describe()' as a new method declaration that shadows the parent method, triggering E07260. See ek9 -h E07260 for details.

Explanation: An override must maintain or widen the access level. Narrowing from protected to private violates Liskov Substitution and triggers E07010. See ek9 -h E07010 for details.

override protected describe()
        -> extra as Integer
        <- rtn as String: "child"

Explanation: The override signature must match the parent exactly. Adding, removing, or changing parameter types means the method does not actually override the parent and triggers E05110. See ek9 -h E05110 for details.


Question: How does abstract implementation flow through a three-level hierarchy?

What is E05070 incorrect use of this in context?

What is E05020 circular hierarchy detection?

What is E05150 purity mismatch in override?

How do abstract intermediate classes work in EK9?

Answer:
Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

EK9 supports multi-level abstract hierarchies where abstract classes can extend other abstract classes, deferring implementation to concrete leaf classes. Each level can implement some abstract methods and declare new ones.

ABSTRACT EXTENDING ABSTRACT
An abstract class can extend another abstract class. The intermediate abstract class may implement some inherited abstract methods while leaving others for descendants.

CONCRETE LEAF REQUIREMENT
The concrete (non-abstract) class at the bottom of the chain MUST implement ALL remaining abstract methods from every ancestor. Missing implementations trigger compile errors.

OPEN MODIFIER REQUIREMENT (E05020)
For a class to be extended, it must be declared 'as open' or 'as abstract'. Abstract implies open. A class not declared as open or abstract cannot be extended.

CORRECT PATTERN
  Shape as abstract           // Level 1: pure abstract
    area() as abstract
  Polygon extends Shape as abstract  // Level 2: still abstract
    sides() as abstract
    override area()            // Implements area
  Triangle extends Polygon     // Level 3: concrete, implements sides()
    override sides()

See Q602 for circular hierarchy detection. See Q605 for this-context rules. See Q567 for purity chain inheritance. See Q606 for method shadowing.

defines module qa.typehierarchy.abstractchain

  defines class

    <?-
      Level 1: Pure abstract base.
      Declares the fundamental operations.
    -?>
    Shape as abstract

      area() as pure abstract
        <- rtn as Float?

      perimeter() as pure abstract
        <- rtn as Float?

      describe() as pure abstract
        <- rtn as String?

      default operator ?

    <?-
      Level 2: Abstract intermediate.
      Implements describe(), leaves area() and perimeter() abstract.
    -?>
    Polygon extends Shape as abstract

      sides() as pure abstract
        <- rtn as Integer?

      override describe() as pure
        <- rtn as String: "polygon"

      default operator ?

    <?-
      Level 3: Concrete leaf.
      Must implement ALL remaining abstract methods.
    -?>
    Rectangle extends Polygon
      width <- Float()
      height <- Float()

      Rectangle()
        ->
          width as Float
          height as Float
        this.width: width
        this.height: height

      override area() as pure
        <- rtn as Float: width * height

      override perimeter() as pure
        <- rtn as Float: (width + height) * 2.0

      override sides() as pure
        <- rtn as Integer: 4

      override describe() as pure
        <- rtn as String: "rectangle"

      default operator ?

    <?-
      Another concrete leaf from the same hierarchy.
    -?>
    EquilateralTriangle extends Polygon
      sideLength <- Float()

      EquilateralTriangle()
        -> sideLength as Float
        this.sideLength: sideLength

      override area() as pure
        <- rtn as Float: (sideLength * sideLength * 0.433)

      override perimeter() as pure
        <- rtn as Float: sideLength * 3.0

      override sides() as pure
        <- rtn as Integer: 3

      override describe() as pure
        <- rtn as String: "equilateral triangle"

      default operator ?

    <?-
      A concrete, non-abstract pair. Panel may extend Widget only while Widget is
      'as open'; drop 'as open' and Widget is closed, so the extension is rejected.
    -?>
    Widget as open
      label <- "widget"

      default operator ?

    Panel extends Widget
      visible <- true

      default operator ?

  defines program

    AbstractChainDemo()
      stdout <- Stdout()

      shapes <- List() of Shape
      shapes += Rectangle(4.0, 3.0)
      shapes += EquilateralTriangle(5.0)

      for shape in shapes
        stdout.println(`${shape.describe()}: area=${shape.area()}`)

Java: abstract class extending abstract class is standard. Kotlin: abstract classes extend abstract classes. Python: ABC can extend ABC. Rust: no inheritance, use trait composition. Go: interface embedding instead. EK9: abstract extends abstract with 'as abstract' or 'as open', concrete leaf must implement all remaining abstract methods.

E05020 E05070 E05150 abstract chain circular concrete hierarchy implementation inherit leaf open type

Explanation: EK9 classes are closed by default. Panel can extend Widget only because Widget is declared 'as open'. Removing 'as open' makes Widget closed, so 'Panel extends Widget' triggers E05030 - not open to be extended. See ek9 -h E05030 for details.

Explanation: A class can only extend a compatible type. Attempting to extend a built-in or incompatible type triggers E05110 because the target type is not valid in this context. See ek9 -h E05110 for details.

override area()
        <- rtn as Float: width * height

Explanation: When overriding a pure abstract method, the override must also be declared pure. Removing purity from the override creates a contract mismatch and triggers E05150. See ek9 -h E05150 for details.


Question: How do pure and non-pure methods coexist in an override hierarchy?

What is E05190 pure constructor mismatch?

What happens when a pure override tries to mutate state?

What happens when a pure method calls a non-pure method?

Can a class have both pure and non-pure methods with overrides?

Answer:
A class can have both pure and non-pure methods. The purity contract applies per-method, not per-class. When overriding, pure methods must stay pure, and non-pure methods can remain non-pure.

PURE METHOD OVERRIDE (E05150)
If a parent method is declared as pure, every override in every descendant MUST also be pure. Removing purity from an override triggers E05150.

NON-PURE METHOD OVERRIDE
If a parent method is NOT pure, overrides are also not pure. A descendant CANNOT add purity to an override that was not pure in the parent (E05160).

MUTATION IN PURE (E08120)
Pure methods cannot mutate fields. Using ':=' on a field, or '+=' on a collection, inside a pure method triggers E08120.

CALLING NON-PURE FROM PURE (E08130)
A pure method cannot call a non-pure method. This transitively ensures that pure methods have no side effects.

PURE CONSTRUCTOR RULE (E05190)
If ANY constructor is pure, ALL constructors must be pure. Mixed constructor purity is not allowed.

See Q560 for pure method basics. See Q561 for purity override rules. See Q567 for purity chain inheritance. See Q566 for pure method restrictions.

defines module qa.purity.overridecontract

  defines class

    <?-
      Base class with both pure and non-pure methods.
      Pure methods define read-only contract.
      Non-pure methods allow state mutation.
      Fields are always private; accessor methods expose state.
    -?>
    Account as open
      balance <- Float()
      accountName <- String()

      Account()
        ->
          accountName as String
          initialBalance as Float
        this.accountName: accountName
        this.balance: initialBalance

      //Pure accessor: subclasses use this to read balance
      getBalance() as pure
        <- rtn as Float: balance

      //Pure accessor: subclasses use this to read name
      getAccountName() as pure
        <- rtn as String: accountName

      //Pure: read-only computation
      formatSummary() as pure
        <- rtn as String: `${accountName}: ${balance}`

      //Non-pure: mutates balance field
      deposit()
        -> amount as Float
        balance: balance + amount

      //Non-pure: mutates balance field
      withdraw()
        -> amount as Float
        if amount <= balance
          balance: balance - amount

      default operator ?

    <?-
      Child class: pure overrides stay pure, non-pure stay non-pure.
      Each override maintains the parent contract.
      Accesses parent state via accessor methods (fields are private).
    -?>
    SavingsAccount extends Account
      interestRate <- Float()

      SavingsAccount()
        ->
          accountName as String
          initialBalance as Float
          interestRate as Float
        super(accountName, initialBalance)
        this.interestRate: interestRate

      //Pure override: stays pure, uses accessor methods for parent state
      override formatSummary() as pure
        <- rtn as String: `Savings ${getAccountName()}: ${getBalance()} @ ${interestRate}%`

      //Non-pure: adds interest via parent's deposit method
      applyInterest()
        earned <- getBalance() * interestRate / 100.0
        deposit(earned)

      default operator ?

  defines program

    PurityOverrideDemo()
      stdout <- Stdout()

      savings <- SavingsAccount("Rainy Day", 1000.0, 2.5)
      stdout.println(savings.formatSummary())

      savings.deposit(500.0)
      savings.applyInterest()
      stdout.println(savings.formatSummary())

Java: no purity enforcement. Kotlin: no purity. Rust: shared references prevent mutation (different mechanism). Python: no purity. EK9: per-method purity with compile-time enforcement across the entire override chain.

E05190 E08120 E08130 abstract contract function immutable mixed mutation non-pure open override pure purity side-effect virtual

Account() as pure
        ->
          accountName as String
          initialBalance as Float
        this.accountName :=? accountName
        this.balance :=? initialBalance

      Account()
        -> accountName as String
        this.accountName: accountName

Explanation: If ANY constructor is declared pure, ALL constructors in the same class must be pure. Mixed constructor purity triggers E05190. See ek9 -h E05190 for details.

formatSummary() as pure
        <- rtn as String: ""
        balance: balance + 1.0
        rtn: `${accountName}: ${$balance}`

Explanation: Pure methods cannot mutate fields. Using ':=' on a field inside a pure method triggers E08100 because it constitutes a side effect. See ek9 -h E08100 for details.

override formatSummary() as pure
        <- rtn as String: ""
        deposit(100.0)
        rtn: `Savings ${getAccountName()}: ${$getBalance()} @ ${$interestRate}%`

Explanation: A pure method cannot call a non-pure method. Since deposit() mutates state, calling it from a pure method triggers E08130. See ek9 -h E08130 for details.


Question: How do sealed classes with allow only work in EK9?

What is E05050 constructor delegation must be first?

What is E05270 sealed class must be open?

How does 'allow only' restrict which classes can extend a base?

Answer:
EK9 supports sealed class hierarchies using 'allow only'. A sealed class declares which types are permitted to extend it. This creates a closed set of subtypes, enabling exhaustive pattern matching.

ALLOW ONLY SYNTAX
The parent class lists permitted subtypes:
  Shape allow only Circle, Rectangle as open
Only Circle and Rectangle may extend Shape. Any other class attempting to extend Shape is rejected.

OPEN REQUIREMENT (E05270)
The sealed class must be declared 'as open' (via the 'allow only' syntax which implies openness). Without it, no class can extend it at all.

CONSTRUCTOR DELEGATION (E05050)
When child classes call super(), the super() call must be the FIRST statement in the constructor body. Code before super() triggers E05050.

OVERRIDE CORRECTNESS (E05100)
Child classes can override parent methods. Using 'override' on a method that does not exist in the parent triggers E05100.

BENEFITS
Sealed hierarchies enable dispatchers to check exhaustiveness. If a dispatcher handles Shape, the compiler knows only Circle and Rectangle exist.

See Q607 for sealed class requirements. See Q609 for sealed across modules. See Q611 for hierarchy validation summary. See Q602 for circular hierarchy detection.

defines module qa.typehierarchy.sealedallowonly

  defines class

    <?-
      Sealed base: only Circle and Square may extend.
      The 'allow only' creates a closed set of subtypes.
    -?>
    Shape allow only Circle, Square as open
      shapeName <- String()

      Shape()
        -> shapeName as String
        this.shapeName: shapeName

      label()
        <- rtn as String: shapeName

      default operator ?

    <?-
      Permitted subtype: Circle.
      Must call super() as FIRST statement.
    -?>
    Circle extends Shape
      radius <- Float()

      Circle()
        -> radius as Float
        super("circle")
        this.radius: radius

      override label()
        <- rtn as String: `circle r=${radius}`

      default operator ?

    <?-
      Permitted subtype: Square.
      Also calls super() first.
    -?>
    Square extends Shape
      sideLength <- Float()

      Square()
        -> sideLength as Float
        super("square")
        this.sideLength: sideLength

      override label()
        <- rtn as String: `square s=${sideLength}`

      default operator ?

  defines class

    <?-
      Renderer uses dispatcher to handle each sealed subtype.
      Dispatchers are class methods, not standalone functions.
    -?>
    ShapeRenderer

      describe() as dispatcher
        -> shape as Shape
        <- description as String: shape.label()

      describe()
        -> shape as Circle
        <- description as String: `Circular: ${shape.label()}`

      describe()
        -> shape as Square
        <- description as String: `Rectangular: ${shape.label()}`

      default operator ?

  defines program

    SealedAllowOnlyDemo()
      stdout <- Stdout()

      renderer <- ShapeRenderer()

      shapes <- List() of Shape
      shapes += Circle(5.0)
      shapes += Square(4.0)

      for shape in shapes
        stdout.println(renderer.describe(shape))

Java: sealed classes with 'permits' (Java 17+). Kotlin: sealed classes with 'sealed' keyword. Rust: enum variants (algebraic data types). Python: no sealed classes. Go: no sealed types. EK9: 'allow only' on class declaration, compiler enforces the closed set.

E05050 E05100 E05270 allow circular class closed exhaustive hierarchy inherit only permitted sealed type

Circle()
        -> radius as Float
        this.radius: radius
        super("circle")

Explanation: When a child class calls super(), the super() call must be the first statement in the constructor body. Placing code before super() triggers E05050. See ek9 -h E05050 for details.

override nonExistentMethod()
        <- rtn as String: "oops"

Explanation: Using 'override' on a method that does not exist in the parent type triggers E05110. The method name must match an actual parent method. See ek9 -h E05110 for details.

Explanation: A sealed class using 'allow only' must be declared 'as open' so that the permitted subtypes can extend it. Without 'as open', no class can extend it at all, which contradicts the 'allow only' declaration and triggers E05270. See ek9 -h E05270 for details.


Question: How does EK9 resolve the diamond problem with trait method conflicts?

What is E05210 dispatcher unreachable handler?

What is E05220 method conflict from multiple traits?

What is E05230 unresolved trait method conflict?

How do I resolve conflicting methods from two traits?

Answer:
When a class implements two traits that declare the same method name, EK9 detects the conflict and requires the class to provide an explicit override to resolve it.

DIAMOND DETECTION (E05220)
If TraitA declares process() and TraitB also declares process(), a class implementing both has a conflict. The compiler detects this and reports E05220 unless the class provides an override.

RESOLUTION VIA OVERRIDE (E05230)
The class must provide its own 'override' of the conflicting method. This removes ambiguity by giving the class full control over the method body. Without the override, the compiler cannot choose between the two trait implementations and reports E05230.

UNREACHABLE DISPATCH (E05210)
In dispatchers, a handler for a type that is not reachable from the base dispatch type is flagged with E05210. This ensures all dispatcher handlers can actually be invoked.

CORRECT PATTERN
  TraitA
    process() ...
  TraitB
    process() ...
  MyClass with trait of TraitA, TraitB
    override process() ...   // Resolves the conflict

See Q107 for multiple trait implementation. See Q579 for trait override chains. See Q611 for hierarchy validation summary.

defines module qa.typehierarchy.diamondtrait

  defines trait

    <?-
      First trait: declares a process() method with default.
    -?>
    Auditable
      auditEntry() as pure
        <- rtn as String: "audited"

      formatRecord() as pure
        <- rtn as String: `[AUDIT] ${auditEntry()}`

    <?-
      Second trait: declares a formatRecord() method too.
      This creates a conflict for classes implementing both traits.
    -?>
    Printable
      printLabel() as pure
        <- rtn as String: "printable"

      formatRecord() as pure
        <- rtn as String: `[PRINT] ${printLabel()}`

  defines class

    <?-
      Class implementing both traits.
      Must override formatRecord() to resolve the diamond conflict.
    -?>
    DocumentRecord with trait of Auditable, Printable
      title <- String()

      DocumentRecord()
        -> title as String
        this.title: title

      override auditEntry() as pure
        <- rtn as String: title

      override printLabel() as pure
        <- rtn as String: title

      //Resolves the diamond: both Auditable and Printable have formatRecord()
      override formatRecord() as pure
        <- rtn as String: `[DOC] ${title}`

      default operator ?

    <?-
      Another class implementing only one trait: no conflict.
    -?>
    SimpleAudit with trait of Auditable
      action <- String()

      SimpleAudit()
        -> action as String
        this.action: action

      override auditEntry() as pure
        <- rtn as String: action

      default operator ?

  defines program

    DiamondTraitDemo()
      stdout <- Stdout()

      doc <- DocumentRecord("Invoice #42")
      stdout.println(doc.formatRecord())
      stdout.println(doc.auditEntry())
      stdout.println(doc.printLabel())

      simple <- SimpleAudit("login")
      stdout.println(simple.formatRecord())

Java: interface default method conflict requires class override. Kotlin: must override and can use super<TraitName>.method(). Python: MRO determines which implementation wins. C++: virtual inheritance for diamond. Rust: explicit disambiguation with <Type as Trait>::method(). EK9: class must provide override for conflicting trait methods.

E05210 E05220 E05230 abstract circular conflict diamond function hierarchy inherit multiple open override resolution trait type virtual

diamond problem
diamond inheritance
multiple inheritance conflict

DocumentRecord with trait of Auditable, Printable, UnrelatedTrait

Explanation: UnrelatedTrait is not defined in this module. Adding an unresolved type to the trait list triggers E50010. See ek9 -h E50010 for details.

//formatRecord() not overridden, leaving the diamond conflict unresolved

Explanation: When two traits provide conflicting method implementations and the implementing class does not override the method to resolve the ambiguity, E06150 is reported. The class must provide its own override. See ek9 -h E06150 for details.