Question: Why does EK9 care about operator placement (prefix vs suffix)?

What triggers E01084 prefix operator wrong position?

What triggers E01085 suffix operator wrong position?

Why does 'list$' fail but '$list' works?

Answer:
EK9 has strict rules about operator position. Some operators must come BEFORE the expression (prefix), and one operator must come AFTER (suffix).

PREFIX OPERATORS (must come BEFORE)
  $value       converts to String
  $$value      converts to JSON
  #? collection   gets length or hashcode
  #< list      gets first element
  #> list      gets last element
  ~ number     negates or reverses
  not flag     boolean negation
  abs number   absolute value
  sqrt number  square root
  empty list   checks if empty
  length name  gets length

SUFFIX OPERATOR (must come AFTER)
  value?       checks if value is set (isSet)

COMMON MISTAKES
  list#<       WRONG: #< is prefix, use '#< list'
  value$       WRONG: $ is prefix, use '$value'
  ?value       WRONG: ? is suffix, use 'value?'

See Q25 for the promote operator. See Q39 for tri-state and isSet.

defines module qa.gettingstarted.operatorplacement

  defines program

    OperatorPlacementDemo()
      -> userName as String
      stdout <- Stdout()

      nameText <- $userName
      stdout.println(nameText)

      nameLength <- length userName
      stdout.println(`Length: ${nameLength}`)

      if userName?
        stdout.println("User name is set")

Java: all operators are infix or prefix (++x, !flag). Python: not is prefix, no suffix operators. Rust: ! is prefix (boolean), ? is suffix (error propagation, similar to EK9). Kotlin: !! is suffix (non-null assertion), ! is prefix. Go: ! is prefix, no suffix operators. EK9: strict prefix/suffix distinction enforced at the grammar level.

E01084 E01085 isSet length operator position prefix string suffix

operator placement
prefix operators
suffix operators

Explanation: The '$' operator is a prefix operator and must come BEFORE the expression. Write '$userName' not 'userName$'. See ek9 -h E01084 for details.

Explanation: The '?' operator is a suffix operator and must come AFTER the expression. Write 'userName?' not '?userName'. See ek9 -h E01085 for details.


Question: What is a logical tautology and why does EK9 reject it?

Why does 'flag or not flag' cause a compiler error?

How does EK9 detect always-true logical expressions?

Answer:
EK9 detects logical tautologies and contradictions at compile time (E08085). A tautology is an expression that is ALWAYS true regardless of operand values. A contradiction is ALWAYS false.

TAUTOLOGY PATTERNS (always true)
  flag or not flag        always true
  not flag or flag        always true

CONTRADICTION PATTERNS (always false)
  flag and not flag       always false
  not flag and flag       always false

WHY REJECTED
These expressions indicate logic errors - typically a copy-paste mistake or incorrect variable name. If always-true is intended, use 'true' directly. If always-false, use 'false'.

CORRECT ALTERNATIVES
  ready or fallback          different variables - valid
  enabled and not expired    different variables - valid

See Q632 for define-before-use. See Q789 for unused variables.

defines module qa.dataflowsafety.logicaltautology

  defines function

    checkAvailability()
      ->
        isReady as Boolean
        hasFallback as Boolean
      <- result as Boolean?

      result: isReady or hasFallback

    checkRequirements()
      ->
        isEnabled as Boolean
        isExpired as Boolean
      <- result as Boolean?

      result: isEnabled and not isExpired

Java: no compiler detection, relies on FindBugs/SpotBugs. Python: no detection, relies on pylint. Rust: clippy warns about tautological comparisons. Kotlin: IntelliJ warns but compiles. Go: go vet does not detect logical tautologies. EK9: compile-time error E08085, code cannot proceed past this check.

E08085 always code contradiction dead false logical tautology true

logical tautology
E08085
dead code detection

Explanation: The expression 'isReady or not isReady' is a logical tautology - it is always true regardless of the value of isReady. Use different variables or simplify to 'true'. See ek9 -h E08085 for details.


Question: Why does EK9 reject comparisons against variables with known constant values?

What triggers E08086 condition always true?

What triggers E08087 condition always false?

How does EK9 track variable values through data flow?

Answer:
EK9 tracks variable assignments through data flow analysis. When a variable is assigned a constant value and then compared, the compiler can determine whether the condition is always true or always false.

ALWAYS TRUE (E08086)
  status <- "active"
  if status == "active"   dead else branch

ALWAYS FALSE (E08087)
  status <- "active"
  if status == "error"    dead if body

WHY REJECTED
Dead branches indicate logic errors. Either the condition is wrong, the variable should have a different value, or the check is unnecessary.

CORRECT PATTERNS
- Use parameters instead of constants when conditions should vary
- Use the constant directly if one branch is never needed
- Check a different variable that can actually change

See Q795 for logical tautology. See Q632 for define-before-use.

defines module qa.dataflowsafety.conditionalways

  defines function

    lookupMode()
      -> modeCode as Integer
      <- rtn as String: String()

      if modeCode > 0
        rtn: "active"
      else
        rtn: "inactive"

    lookupChannel()
      -> channelId as Integer
      <- rtn as String: String()

      if channelId > 0
        rtn: "open"
      else
        rtn: "closed"

    checkMode()
      -> modeCode as Integer
      <- label as String: String()

      currentMode <- lookupMode(modeCode)

      if currentMode == "active"
        label: "System is active"
      else
        label: "System is inactive"

    checkChannel()
      -> channelId as Integer
      <- label as String: String()

      channelState <- lookupChannel(channelId)

      if channelState == "closed"
        label: "Channel closed"
      else
        label: "Channel available"

Java: no compiler detection, relies on FindBugs. Python: no detection. Rust: compiler does not warn for this pattern. Kotlin: IntelliJ may warn but compiles. Go: no detection. EK9: compile-time error E08086/E08087, prevents dead branches from reaching production.

E08086 E08087 always branch condition dead false flow true

condition always true
condition always false
data flow analysis

Explanation: When the compiler tracks that 'currentMode' was assigned the constant 'active', the condition 'currentMode == "active"' is always true, making the else branch unreachable dead code. Use a computed value instead. See ek9 -h E08086 for details.

Explanation: When the compiler tracks that 'channelState' was assigned the constant 'open', the condition 'channelState == "closed"' is always false, making the if body unreachable dead code. Use a computed value instead. See ek9 -h E08087 for details.


Question: How does EK9 detect circular component injection at compile time?

What triggers E08190 DI circular dependency?

Why does mutual component injection fail to compile?

How do I break a circular dependency chain in EK9 DI?

Answer:
EK9 detects circular dependency chains in component injection at compile time (E08190). When Component A depends on Component B, and Component B depends on Component A (directly or transitively), neither can be constructed first.

CIRCULAR CHAIN
  ComponentA -> needs AbstractB -> provided by ComponentB
  ComponentB -> needs AbstractA -> provided by ComponentA
  DEADLOCK: neither can be created first

COMPILE-TIME DETECTION
Unlike Spring (runtime BeanCurrentlyInCreationException), EK9 catches circular dependencies before any code runs. The compiler analyses the full injection graph and rejects cycles.

BREAKING CYCLES
1. One-way dependency: only one component injects the other
2. Extract shared logic into a third component
3. Use an event or callback pattern instead of direct injection

See Q671 for the general circular dependency explanation. See Q673 for missing registration. See Q674 for duplicate registration.

defines module qa.divalidation.circularcompiletime

  defines component

    Handler as abstract

      handleEvent() as abstract
        -> eventName as String
        <- handled as Boolean?

      default operator ?

    NotificationHandler extends Handler

      override handleEvent()
        -> eventName as String
        <- handled as Boolean: true

      default operator ?

    Dispatcher as abstract

      dispatch() as abstract
        -> eventName as String
        <- dispatched as Boolean?

      default operator ?

    EventDispatcher extends Dispatcher

      handler as Handler!

      override dispatch()
        -> eventName as String
        <- dispatched as Boolean?

        dispatched: handler.handleEvent(eventName)

      default operator ?

  defines application

    OneWayApp
      register NotificationHandler() as Handler
      register EventDispatcher() as Dispatcher

  defines program

    CircularDiDemo() with application of OneWayApp
      stdout <- Stdout()

      dispatcher as Dispatcher!
      sent <- dispatcher.dispatch("user-login")
      stdout.println(`Dispatched: ${sent}`)

Java: Spring throws BeanCurrentlyInCreationException at runtime. Python: no DI framework detection. Go: Wire detects at code generation time. Rust: no standard DI. Kotlin: Koin throws at runtime. EK9: compile-time E08190 rejects circular chains before deployment.

DI E08190 circular compile-time component cycle deadlock dependency injection

circular dependency
E08190
DI compile-time validation

    NotificationHandler extends Handler

      dispatcher as Dispatcher!

      override handleEvent()

Explanation: Injecting Dispatcher into NotificationHandler creates a circular dependency (Handler -> Dispatcher -> Handler) that the compiler rejects at compile time. See ek9 -h E08190 for details.


Question: What happens with missing or duplicate component registrations?

What triggers E08210 missing registration?

What triggers E08230 duplicate registration?

How does EK9 validate application registrations at compile time?

Answer:
EK9 validates application registrations at compile time for two common errors.

MISSING REGISTRATION (E08210)
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 'register' declaration.

DUPLICATE REGISTRATION (E08230)
When an application registers two concrete implementations for the same abstract type, the compiler raises E08230. Each abstract type must have exactly ONE registration per application.

CORRECT PATTERN
- Register exactly one concrete implementation per abstract type
- Use separate applications for different configurations
- Ensure all transitive dependencies are registered

See Q673 for missing registration details. See Q674 for duplicate registration details. See Q671 for circular dependencies.

defines module qa.divalidation.missingduplicate

  defines component

    CacheLayer as abstract

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

      default operator ?

    ConcreteCache extends CacheLayer

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

      default operator ?

    Repository as abstract

      fetch() as abstract
        -> recordId as String
        <- fetched as String?

      default operator ?

    ConcreteRepo extends Repository

      override fetch()
        -> recordId as String
        <- fetched as String: "record-" + recordId

      default operator ?

  defines application

    CompleteConfig
      register ConcreteCache() as CacheLayer
      register ConcreteRepo() as Repository

  defines program

    RegistrationDemo() with application of CompleteConfig
      stdout <- Stdout()

      cacheService as CacheLayer!
      repoService as Repository!

      cachedItem <- cacheService.lookup("item-1")
      stdout.println(`Cache: ${cachedItem}`)

      record <- repoService.fetch("rec-1")
      stdout.println(`Repo: ${record}`)

Java: Spring throws NoSuchBeanDefinitionException (missing) or NoUniqueBeanDefinitionException (duplicate) at runtime. Python: runtime errors. Go: Wire detects at generation time. Rust: no standard DI. Kotlin: Koin throws at runtime. EK9: compile-time E08210/E08230 prevents both errors before deployment.

DI E08210 E08230 application component duplicate inject missing registration

missing registration
duplicate registration
DI validation

Explanation: The program injects both CacheLayer and Repository, but only CacheLayer is registered. Every injection point must have a matching registration. Add 'register ConcreteRepo() as Repository' to the application. See ek9 -h E08210 for details.


Question: Why does EK9 require named arguments when passing multiple Boolean literals?

What triggers E11061 boolean arguments require names?

Why does 'connect(true, false)' fail to compile?

How do I pass multiple Boolean parameters in EK9?

Answer:
When a call passes two or more Boolean literal values (true/false) as positional arguments, EK9 raises E11061. Boolean literals carry zero semantic signal at a call site.

WHY AMBIGUOUS
  connect(true, false)    What does each Boolean mean?
  connect(true, true)     Completely opaque without checking the signature

SINGLE BOOLEAN OK
  setVisible(true)        Clear from context - one Boolean is tolerable

FIX: USE NAMED ARGUMENTS
  connect(useSsl: true, autoReconnect: false)
  When naming any argument, EK9 requires ALL arguments to be named.

See Q777 for discarded operator returns. See Q310 for code quality overview.

defines module qa.codequality.booleanargs

  defines function

    configureNetwork()
      ->
        hostName as String
        useSsl as Boolean
        autoReconnect as Boolean
      <- summary as String: String()

      sslLabel <- "no-ssl"
      if useSsl
        sslLabel: "ssl"

      reconnectLabel <- "no-reconnect"
      if autoReconnect
        reconnectLabel: "reconnect"

      summary: `${hostName} [${sslLabel}, ${reconnectLabel}]`

  defines program

    BooleanArgDemo()
      stdout <- Stdout()

      networkInfo <- configureNetwork(hostName: "db.example.com", useSsl: true, autoReconnect: false)
      stdout.println(networkInfo)

Java: silently allows positional Boolean arguments. Python: allows but style guides recommend kwargs. Swift: requires argument labels by default. Rust: no named arguments (use builder pattern). Kotlin: supports named arguments but doesn't require them. Go: no named arguments. EK9: compile-time error when 2+ Boolean literals appear as positional arguments.

E11061 ambiguous argument boolean call named positional readability site

boolean arguments
E11061
named arguments

Explanation: Passing two Boolean literals as positional arguments is ambiguous - the reader cannot tell what 'true, false' means without checking the function signature. Use named arguments for clarity. See ek9 -h E11061 for details.


Question: Why does EK9 reject repeated literal values?

What triggers E11065 repeated magic literal?

How many times can I use the same string literal?

Why must repeated values be extracted to constants?

Answer:
EK9 raises E11065 when the same literal value appears 3 or more times in a file. Repeated literals are a maintenance liability.

WHY REJECTED
When a literal needs to change, every occurrence must be found and updated. Research (Bettenburg & Shang, 2010) found that ~50% of changes to duplicated code are inconsistent - only some instances get updated.

THRESHOLDS
- 3+ occurrences in a single file: error
- 4+ occurrences across files in the same module: error

FIX: EXTRACT TO CONSTANT OR VARIABLE
  //Before: repeated literal
  stdout.println("processing")
  stdout.println("processing")
  stdout.println("processing")

  //After: named constant
  statusMsg <- "processing"
  stdout.println(statusMsg)
  stdout.println(statusMsg)
  stdout.println(statusMsg)

See Q799 for boolean argument naming. See Q777 for discarded returns.

defines module qa.codequality.repeatedliteral

  defines program

    MagicLiteralDemo()
      stdout <- Stdout()

      statusMsg <- "processing"
      stdout.println(statusMsg)
      stdout.println(statusMsg)
      stdout.println(statusMsg)

Java: no compiler detection, relies on SonarQube S1192 or PMD. Python: no detection, relies on pylint. Rust: no detection for repeated literals. Kotlin: no compiler detection. Go: no detection. EK9: compile-time error E11065 at threshold of 3 per file or 4 per module.

E11065 constant duplication extract literal magic maintenance repeated

repeated magic literal
E11065
named constants

      stdout.println("processing")
      stdout.println("processing")
      stdout.println("processing")

Explanation: The same literal 'processing' appears 3 times in the file. Extract it to a named variable or constant to avoid maintenance errors when the value needs to change. See ek9 -h E11065 for details.


Question: Why does EK9 reject overrides identical to the parent method?

What triggers E11044 redundant override?

Why can't I override a method with the same body?

What does redundant override mean in EK9?

Answer:
EK9 raises E11044 when an override method body is textually identical to the method it overrides in the parent class. The override adds no value and duplicates inherited behaviour.

WHY REJECTED
- Copy-paste from the parent class without modification
- Incomplete refactoring where the override was meant to change behaviour
- Misunderstanding of inheritance (overrides are only needed to CHANGE behaviour)

FIX OPTIONS
1. Remove the override entirely - inherited implementation provides identical behaviour
2. Actually modify the method body to add new behaviour
3. Call super and add additional logic

See Q800 for repeated literals. See Q777 for discarded returns.

defines module qa.codequality.redundantoverride

  defines class

    Formatter as open

      format()
        -> inputText as String
        <- formatted as String: String()

        formatted: `[${inputText}]`

      default operator ?

    EnhancedFormatter extends Formatter

      override format()
        -> inputText as String
        <- formatted as String: String()

        formatted: "Enhanced: " + inputText

      default operator ?

  defines program

    RedundantOverrideDemo()
      stdout <- Stdout()

      formatter <- EnhancedFormatter()
      styled <- formatter.format("hello")
      stdout.println(styled)

Java: no compiler detection, IntelliJ warns. Python: no detection. Rust: no inheritance, uses trait composition. Kotlin: no detection, IntelliJ warns. Go: no inheritance. EK9: compile-time error E11044, redundant overrides cannot exist.

E11044 copy duplicate identical inherited override parent paste redundant

Explanation: The override body is textually identical to the parent Formatter.format() method. Either remove the override to inherit the parent behaviour, or change the body to provide genuinely different behaviour. See ek9 -h E11044 for details.


Question: Why does EK9 reject manual close of local resources?

What triggers E81055 manual close of local resource?

Why must I use try-with-resources for local variables?

How do I properly close resources in EK9?

Answer:
EK9 raises E81055 when you manually close a locally-declared resource. Manual close is unsafe because if an exception occurs before the close statement, the resource leaks.

WHY UNSAFE
  resource <- MyResource("test")
  doWork()         //exception here?
  close resource   //might never execute!

Research shows ~32% of resource leak bugs are caused by exceptions occurring before manual close() calls.

CORRECT: TRY-WITH-RESOURCES
  try
    -> resource <- MyResource("test")
    doWork()
  //close() GUARANTEED to be called

WHAT IS ALLOWED
- Closing a PARAMETER (caller transferred ownership)
- Closing a FIELD (aggregate manages its own resources)
- try-with-resources for local resources

See Q137 for try-with-resources syntax. See Q803 for abandoned resource detection.

defines module qa.errorhandling.manualclose

  defines class

    DatabaseConn
      connName <- String()

      DatabaseConn()
        -> connectionName as String
        connName: connectionName

      query()
        -> queryText as String
        <- queryResult as String: `${queryText} from ${connName}`

      operator close as pure
        require true

      override operator ? as pure
        <- rtn <- true

  defines function

    safeResourceUsage()
      stdout <- Stdout()

      try
        -> conn <- DatabaseConn("test-db")
        queryResult <- conn.query("SELECT 1")
        stdout.println(queryResult)

Java: allows manual close() but try-with-resources recommended since Java 7. Python: 'with' statement recommended but manual close allowed. Rust: RAII with Drop trait, automatic cleanup. Go: defer close() pattern, easy to forget. C#: using statement for IDisposable. EK9: compile-time error E81055, manual close of locals is impossible.

E81055 close leak local manual resource resources try with

      conn <- DatabaseConn("test-db")
      queryResult <- conn.query("SELECT 1")
      stdout.println(queryResult)
      close conn

Explanation: Manual close of a locally-declared resource is unsafe. If an exception occurs before the close statement, the resource leaks. Use try-with-resources instead. See ek9 -h E81055 for details.


Question: What happens when a closeable resource is never closed?

What triggers E81056 resource never closed or escaped?

How does EK9 detect abandoned resources?

Why must closeable resources be closed or transferred?

Answer:
EK9 raises E81056 when a variable with a close() operator is created but never closed and does not escape the current scope. This is a guaranteed resource leak.

WHAT COUNTS AS ESCAPE
A resource escapes (ownership transferred) when:
- RETURNED: assigned to return variable
- STORED: assigned to a field or collection
- PASSED: given to another method as argument
- WRAPPED: placed in Optional, Result, or container

FIX OPTIONS
1. try-with-resources: automatic close when scope ends (recommended)
2. Return it: transfer ownership to the caller
3. Pass it: give to another component for management
4. Store it: place in a container for batch management

Research shows ~30% of resource leak bugs are caused by developers forgetting to close resources entirely.

See Q802 for manual close rejection. See Q137 for try-with-resources syntax.

defines module qa.errorhandling.resourceneverclosed

  defines class

    FileConn
      fileName <- String()

      FileConn()
        -> name as String
        fileName: name

      readAll()
        <- content as String: "contents of " + fileName

      operator close as pure
        require true

      override operator ? as pure
        <- rtn <- true

  defines function

    safeFileRead()
      stdout <- Stdout()

      try
        -> fileConn <- FileConn("log.txt")
        content <- fileConn.readAll()
        stdout.println(content)

Java: unclosed resources only detected by FindBugs/SpotBugs, not the compiler. Python: no detection, relies on linters. Rust: Drop trait guarantees cleanup. Go: no detection (defer is voluntary). C#: Roslyn CA2000 warns but code still compiles. EK9: compile-time error E81056, abandoned resources cannot exist.

E81056 abandoned close escape leak ownership resource transfer try

abandoned resource
E81056
resource leak detection

Explanation: A closeable resource was created but never closed via try-with-resources and does not escape the scope. This is a guaranteed resource leak. Wrap it in try-with-resources. See ek9 -h E81056 for details.


Question: Why does EK9 have a built-in test framework instead of using JUnit or pytest?

Why not use an external test framework with EK9?

What is the EK9 equivalent of JUnit or pytest?

Answer:
EK9 builds testing into the compiler and language, eliminating the need for JUnit, pytest, or any external test framework. This is a deliberate design choice.

WHY BUILT-IN
External frameworks create a gap between what the compiler knows and what the test runner knows. EK9 closes that gap:
- The compiler validates test quality at compile time (no empty tests, no orphan assertions)
- Coverage instrumentation is built into the compilation pipeline
- No dependency management for test libraries
- No version conflicts between test framework and language

COMPARISON
Java: JUnit 5 is a separate dependency, needs Maven/Gradle integration, assertion methods imported from org.junit.jupiter.api. Python: pytest is pip-installed, uses assert rewriting magic. Rust: #[test] is built-in but coverage requires external tools (tarpaulin). Go: testing package is built-in — closest to EK9's approach.

EK9 goes further than Go: compile-time test quality validation (E81007 empty test, E81011 orphan assertion), built-in coverage with 80% threshold, HTML dashboard, and test grouping for parallel/sequential control.

THE QUALITY ADVANTAGE
Because the compiler sees your tests, it can enforce quality:
- Tests must actually verify something (assert, assertThrows, or expected_output.txt)
- Assertions must be reachable from a @Test program (call graph analysis)
- Assertions cannot appear in production code
- All code quality metrics (complexity, cohesion, coupling) apply to test code too

No external framework can provide this level of integration.

See Q155 for writing tests. See Q206 for coverage. See Q207 for output formats.

defines module qa.testdeep.why.builtin

  defines function

    calculate() as pure
      -> n as Integer
      <- rtn as Integer: n * n

  defines program

    // === EK9: NO FRAMEWORK NEEDED ===
    // No imports, no dependencies, no build plugin.
    // Just @Test and assert.

    @Test
    SquareOfFiveTest()
      result <- calculate(5)
      assert result == 25

    // === GROUPED TESTS RUN SEQUENTIALLY ===

    @Test: "math"
    SquareOfThreeTest()
      result <- calculate(3)
      assert result == 9

    @Test: "math"
    SquareOfZeroTest()
      result <- calculate(0)
      assert result == 0

Java: JUnit 5 requires org.junit.jupiter dependency, @Test annotation, Assertions.* static imports, build tool plugin. Python: pytest requires pip install, conftest.py configuration, -v flags. Rust: #[test] is built-in but cargo-tarpaulin needed for coverage. Go: testing.T built-in, go test -cover for coverage. EK9: everything built in — @Test, assert, assertThrows, coverage, HTML reports, quality validation.

advantage built-in comparison coverage framework junit migrate pytest quality testing

junit equivalent
pytest equivalent
built-in testing

@Test
    EmptyTest()
      result <- calculate(5)
      stdout <- Stdout()
      stdout.println($result)

Explanation: A @Test program must validate something: assert statements, assertThrows, or an expected_output.txt file. Printing output without a companion expected file is an empty test. See ek9 -h E81007 for details.


Question: How do I organise tests relative to production code in EK9?

Where do I put test files in an EK9 project?

How does EK9 separate test code from production code?

Answer:
EK9 uses a dev/ directory convention to separate test code from production code. Tests live alongside the code they test, not in a separate tree.

DIRECTORY STRUCTURE
An EK9 project typically has:
  myProject/
    myModule.ek9          <- production code
    dev/
      tests.ek9           <- test code for myModule
      expected_output.txt  <- optional: for black-box tests

THE dev/ DIRECTORY
Files in dev/ are only compiled when running tests (ek9 -t). They are excluded from production builds. This means:
- Test code never ships to production
- Test utilities, helpers, and fixtures stay separate
- No #ifdef or conditional compilation needed

TESTS LIVE WITH THEIR CODE
Unlike Java (src/test separate tree) or Python (tests/ directory), EK9 tests sit next to the code they test. This makes it easy to find tests and keeps related code together.

MULTIPLE TEST FILES
You can have multiple test files in dev/:
  myProject/
    myModule.ek9
    dev/
      unitTests.ek9
      integrationTests.ek9
      expected_output.txt

EXPECTED OUTPUT FILES
For black-box tests, put the expected output file alongside the test:
- Single case: expected_output.txt
- Multiple cases: expected_case_1.txt, expected_case_2.txt

See Q155 for writing tests. See Q204 for black-box tests. See Q205 for parameterized tests.

defines module qa.testdeep.organisation

  defines function

    greet() as pure
      -> name as String
      <- rtn as String: `Hello, ${name}!`

  defines program

    // === PRODUCTION CODE + TEST STRUCTURE ===
    // In a real project:
    //   myApp/
    //     greeting.ek9     <- this production code
    //     dev/
    //       tests.ek9      <- test code (only compiled with -t)

    TestOrganisationDemo()
      stdout <- Stdout()

      // Production function being tested
      message <- greet("EK9")
      stdout.println(message)

      // Test programs would be in dev/tests.ek9:
      //   @Test
      //   GreetingTest()
      //     result <- greet("World")
      //     assert result == "Hello, World!"

      stdout.println("Tests live in dev/ directory")
      stdout.println("Excluded from production builds")
      stdout.println("Compiled only with ek9 -t")

Java: src/main/java and src/test/java separate trees, mirrored package structure. Python: tests/ directory or same package with test_ prefix. Rust: #[cfg(test)] mod tests in same file, or tests/ directory. Go: _test.go suffix in same package. EK9: dev/ directory alongside production code, excluded from production builds automatically.

dev directory layout organise production project separate structure test

test organisation
dev directory
project structure

stdout.println("test output")
      stdout <- Stdout()

Explanation: Variables must be declared before use. Stdout must be created before calling println on it. See ek9 -h E50010 for details.


Question: How do I group tests and control execution order in EK9?

How do I run a specific subset of tests in EK9?

Answer:
EK9 test grouping controls whether tests run in parallel or sequentially, and lets you run subsets.

UNGROUPED TESTS RUN IN PARALLEL
Tests without a group name run in parallel:
  @Test
  TestA()
    assert 1 + 1 == 2
  @Test
  TestB()
    assert 2 + 2 == 4
TestA and TestB may run simultaneously on different threads.

GROUPED TESTS RUN SEQUENTIALLY
Tests in the same group run one after another:
  @Test: "database"
  SetupDbTest()
    // runs first
  @Test: "database"
  QueryDbTest()
    // runs after SetupDbTest
This guarantees ordering within the group. Different groups still run in parallel.

RUNNING A SPECIFIC GROUP
Use -tg to run only tests in a named group:
  ek9 -tg database myApp.ek9
This skips all tests not in the "database" group.

LISTING TESTS WITHOUT RUNNING
Use -tL to discover tests without executing them:
  ek9 -tL myApp.ek9
Shows all test programs and their groups. Combine with -tg:
  ek9 -tL -tg database myApp.ek9

WHEN TO USE GROUPS
- Tests that share state (database, files): group them sequentially
- Tests that modify shared resources: group to avoid race conditions
- Fast independent tests: leave ungrouped for parallel speed

See Q155 for basic testing. See Q207 for output formats. See Q804 for why built-in.

defines module qa.testdeep.grouping

  defines function

    initializeState() as pure
      <- rtn as String: "ready"

    processItem() as pure
      -> item as String
      <- rtn as String: `processed: ${item}`

  defines program

    // === UNGROUPED: RUN IN PARALLEL ===

    @Test
    FastCheckA()
      assert 1 + 1 == 2

    @Test
    FastCheckB()
      assert 2 * 3 == 6

    // === GROUPED: RUN SEQUENTIALLY ===

    @Test: "workflow"
    WorkflowStepOne()
      state <- initializeState()
      assert state == "ready"

    @Test: "workflow"
    WorkflowStepTwo()
      result <- processItem("data")
      assert result == "processed: data"

    // ek9 -tg workflow myApp.ek9  <- runs only "workflow" group
    // ek9 -tL myApp.ek9           <- lists all tests and groups

Java: JUnit @Tag for filtering, @TestMethodOrder for ordering, @Execution(SAME_THREAD) for sequential. Python: pytest -k for filtering, pytest-ordering for order control. Rust: #[test] runs in parallel by default, --test-threads=1 for sequential. Go: t.Run() subtests, -run regex filtering. EK9: @Test: "group" for sequential grouping, -tg for filtering, ungrouped tests run in parallel.

execution filter group list order parallel sequential subset tL tg

test grouping
test group
sequential tests


Question: How do I run specific tests or list available tests in EK9?

How do I filter which tests to run in EK9?

How do I run just one test group in EK9?

Answer:
EK9 provides the -tg flag to filter by group and -tL to list tests without running them.

RUN ALL TESTS
  ek9 -t myApp.ek9
Runs every @Test program in the project.

RUN A SPECIFIC GROUP
  ek9 -tg database myApp.ek9
Runs only tests marked @Test: "database". All other tests are skipped.

LIST TESTS WITHOUT RUNNING
  ek9 -tL myApp.ek9
Discovery mode. Lists all @Test programs with their group names but does not execute them. Useful for CI/CD pipelines that need to enumerate tests before running.

COMBINE FLAGS
Group filter + list:
  ek9 -tL -tg network myApp.ek9
Group filter + format:
  ek9 -t2 -tg api myApp.ek9
Group filter + profiling:
  ek9 -tp -tg perf myApp.ek9

OUTPUT FORMATS
All output format flags work with -tg:
  -t0  terse    -t1  human (default)
  -t2  JSON     -t3  JUnit XML
  -t4  coverage -t5  verbose coverage
  -t6  HTML     Append p for profiling

CI/CD WORKFLOW
A typical CI pipeline might:
  ek9 -tL myApp.ek9           # list tests
  ek9 -t2 myApp.ek9           # run all, JSON output
  ek9 -t3 myApp.ek9           # JUnit XML for CI dashboards
  ek9 -t6 myApp.ek9           # HTML report as artifact

See Q806 for test grouping. See Q207 for output formats. See Q206 for coverage.

defines module qa.testdeep.running.specific

  defines function

    fetchUser() as pure
      -> userId as Integer
      <- rtn as String: `user-${userId}`

  defines program

    // === DIFFERENT TEST GROUPS ===

    @Test: "api"
    FetchUserTest()
      user <- fetchUser(42)
      assert user == "user-42"

    @Test: "api"
    FetchAnotherUserTest()
      user <- fetchUser(1)
      assert user == "user-1"

    @Test: "validation"
    ValidateInputTest()
      name <- "EK9"
      assert name?
      assert name.length() > 0

    // Run only api tests:     ek9 -tg api thisFile.ek9
    // Run only validation:    ek9 -tg validation thisFile.ek9
    // List all tests:         ek9 -tL thisFile.ek9
    // JSON output for CI:     ek9 -t2 thisFile.ek9
    // HTML report:            ek9 -t6 thisFile.ek9

Java: JUnit -t tag filtering, Maven -Dtest=ClassName, Gradle --tests filter. Python: pytest -k expression, pytest -m marker. Rust: cargo test test_name, --test-threads=1. Go: go test -run TestRegex. EK9: -tg groupname for group filtering, -tL for test discovery.

ci filter format group list pipeline run specific tL tg

run specific tests
test filtering
test list


Question: How do I mock dependencies in EK9 tests without Mockito?

How do I use traits for test doubles in EK9?

How do I test components with injected dependencies in EK9?

Answer:
EK9 does not need Mockito, unittest.mock, or any mocking library. Instead, you use traits and dependency injection to create test doubles at the language level.

THE PATTERN
1. Define behaviour as a trait (interface)
2. Production code depends on the trait, not the concrete implementation
3. In tests, provide a test implementation of the trait
4. DI wiring in the application block selects which implementation to use

WHY NO MOCKING FRAMEWORK
Mocking frameworks exist because languages allow tight coupling to concrete classes. EK9 prevents this:
- Components depend on traits (interfaces), not concrete types
- The DI system wires implementations at compile time
- Test implementations are just another implementation of the trait
- No reflection, no bytecode manipulation, no proxy objects

TRAIT-BASED TEST DOUBLES
Define a trait:
  DataStore as trait
    fetch() as pure abstract
      -> key as String
      <- rtn as String?
Production implementation:
  RealDataStore is DataStore
    override fetch() as pure
      -> key as String
      <- rtn as String: lookupDatabase(key)
Test implementation:
  FakeDataStore is DataStore
    override fetch() as pure
      -> key as String
      <- rtn as String: "test-value"

TEST APPLICATION
Wire the test double in a test application block:
  defines application
    TestApp
      register FakeDataStore() as DataStore

ADVANTAGES
- Compile-time verified: missing method implementations caught immediately
- No magic: test doubles are normal classes implementing traits
- Reusable: same test double works across all tests
- Type-safe: cannot accidentally mock the wrong method signature

See Q227 for DI basics. See Q324 for DI deep dive. See Q155 for basic testing.

defines module qa.testdeep.mocking

  defines trait

    <?-
      The trait defines the contract.
      Production and test code both implement this.
    -?>
    Formatter
      format() as pure abstract
        -> number as Integer
        <- rtn as String?

  defines class

    // === PRODUCTION IMPLEMENTATION ===

    FancyFormatter with trait of Formatter
      override format() as pure
        -> number as Integer
        <- rtn as String: `[${number}]`

    // === TEST DOUBLE ===

    SimpleFormatter with trait of Formatter
      override format() as pure
        -> number as Integer
        <- rtn as String: $number

  defines program

    MockingDemo()
      stdout <- Stdout()

      // Production: uses FancyFormatter
      fancy <- FancyFormatter()
      stdout.println(fancy.format(42))

      // Test: uses SimpleFormatter (the "mock")
      simple <- SimpleFormatter()
      stdout.println(simple.format(42))

      // Both implement Formatter trait
      // DI wiring chooses which one:
      //   Production app: register FancyFormatter() as Formatter
      //   Test app:       register SimpleFormatter() as Formatter

      stdout.println("No Mockito needed")
      stdout.println("Traits replace mocking frameworks")

Java: Mockito mock()/when()/verify(), Spring @MockBean. Python: unittest.mock.patch(), MagicMock. Rust: mockall crate with #[automock]. Go: interfaces + hand-written fakes (standard pattern). Kotlin: MockK or Mockito-kotlin. EK9: trait implementations as test doubles, DI wiring selects implementation, no mocking library needed.

dependency double fake injection interface mock mockito stub test trait

Explanation: A class implements a trait with 'with trait of', not 'extends' — using 'extends' on a trait is an incompatible genus (class vs trait). See ek9 -h E50020 for details.


Question: How does EK9 enforce test quality at compile time?

What is an empty test error in EK9?
What is an orphan assertion in EK9?
How does EK9 prevent meaningless tests?

Answer:
EK9 validates test quality during compilation. Tests that verify nothing or have unreachable assertions are compiler errors, not warnings.

E81007: EMPTY TEST
A @Test program must validate something. If it has no assert statements, no assertThrows, and no expected_output.txt companion file, the compiler rejects it:
  @Test
  EmptyTest()
    result <- calculate(5)
    stdout <- Stdout()
    stdout.println($result)   // prints but verifies nothing
This fails with E81007. Fix by adding assert result == 25 or providing expected_output.txt.

E81011: ORPHAN ASSERTION
Assertions must be reachable from at least one @Test program through the call graph:
  helperFunction()
    assert someCondition()    // orphan — no @Test calls this
If no @Test program calls helperFunction() directly or indirectly, the assert is unreachable and the compiler reports E81011. This catches forgotten test wiring.

E81012: PRODUCTION ASSERTION
Assert and assertThrows are only valid in @Test programs or functions called from @Test programs. Using them in production code is a compiler error.

QUALITY METRICS APPLY TO TESTS
Test code is held to the same quality standards as production code:
- Complexity limits (cyclomatic, cognitive, nesting)
- Cohesion and coupling checks
- Variable naming rules (no temp, flag, data, value)
- Reference ordering (alphabetical)
- Unused parameter detection

WHY THIS MATTERS
In Java/Python, a test that calls code but asserts nothing passes silently — false coverage that hides bugs. EK9 makes this a compile error. You cannot have a test that does not test.

See Q804 for why built-in testing. See Q155 for writing tests. See Q310 for code quality metrics.

defines module qa.testdeep.quality.enforcement

  defines function

    compute() as pure
      -> n as Integer
      <- rtn as Integer: n * n

  defines program

    // === VALID TESTS: ASSERT SOMETHING ===

    @Test
    ComputeSquareTest()
      result <- compute(7)
      assert result == 49

    @Test
    ComputeZeroTest()
      result <- compute(0)
      assert result == 0

    TestQualityDemo()
      stdout <- Stdout()
      stdout.println("E81007: empty test (no assertions)")
      stdout.println("E81011: orphan assertion (unreachable)")
      stdout.println("E81012: assert in production code")
      stdout.println("All quality metrics apply to test code")

Java: JUnit has no compile-time quality checks — empty tests pass silently. SonarQube can detect some patterns but is optional. Python: pytest allows empty tests, flake8 cannot detect missing assertions. Rust: empty #[test] functions compile and pass. Go: empty Test functions compile and pass. EK9: compile-time rejection of empty tests (E81007), orphan assertions (E81011), and production assertions (E81012).

E81007 E81011 E81012 assertion compile empty enforce orphan quality validate

test quality
empty test
orphan assertion

@Test
    NoVerification()
      result <- compute(10)
      stdout <- Stdout()
      stdout.println($result)

Explanation: A @Test program with no assert, no assertThrows, and no expected_output.txt is an empty test — it executes code but validates nothing. Add assertions or provide an expected output file. See ek9 -h E81007 for details.

verifyResult()
      -> value as Integer
      assert value > 0
    // No @Test program calls verifyResult

Explanation: An assert statement must be reachable from at least one @Test program through the call graph. An unreachable assertion is dead test code. See ek9 -h E81011 for details.


Question: How do I test output containing dynamic values like dates or GUIDs in EK9?

What are output placeholders in EK9 tests?

How do I handle non-deterministic output in EK9 black-box tests?

How do I use {{Date}} and {{GUID}} in expected output files?

Answer:
EK9 expected output files support typed placeholders for values that change between test runs. This lets you black-box test programs that print timestamps, GUIDs, or other dynamic data.

THE PROBLEM
A program that prints the current date produces different output each run:
  stdout.println("Started at: " + $DateTime())
The expected_output.txt cannot contain a fixed date string.

THE SOLUTION: PLACEHOLDERS
Use typed placeholders in expected output files:
  Started at: {{DateTime}}
The test runner matches any valid DateTime value at that position.

AVAILABLE PLACEHOLDERS
  {{String}}         any string value
  {{Integer}}        any integer
  {{Float}}          any float
  {{Boolean}}        true or false
  {{Date}}           any valid date
  {{Time}}           any valid time
  {{DateTime}}       any valid date-time
  {{Duration}}       any duration
  {{Millisecond}}    any millisecond value
  {{Money}}          any money value
  {{Colour}}         any colour value
  {{Dimension}}      any dimension value
  {{GUID}}           any GUID/UUID
  {{FileSystemPath}} any file path

EXAMPLE EXPECTED OUTPUT FILE
  Report generated: {{DateTime}}
  Request ID: {{GUID}}
  Items processed: {{Integer}}
  Success: {{Boolean}}

MIXING FIXED AND DYNAMIC
Placeholders can appear anywhere in a line:
  User alice created at {{DateTime}} with ID {{GUID}}
Fixed text is matched exactly. Placeholders match their typed pattern.

See Q204 for black-box testing. See Q205 for parameterized tests. See Q208 for more placeholder details.

defines module qa.testdeep.output.placeholders

  defines program

    // === PROGRAM WITH DYNAMIC OUTPUT ===
    // The expected_output.txt companion would use placeholders
    // to match non-deterministic values.

    PlaceholderDemo()
      stdout <- Stdout()

      // These values change every run
      now <- DateTime()
      requestId <- GUID()
      itemCount <- 42

      stdout.println("Report generated: " + $now)
      stdout.println("Request ID: " + $requestId)
      stdout.println("Items processed: " + $itemCount)
      stdout.println("Success: true")

Java: JUnit requires custom matchers or regex assertions for dynamic values. Python: pytest approx() for floats, custom assertion helpers for dates. Rust: no built-in equivalent, custom comparison functions. Go: testify suite with custom matchers. EK9: built-in typed placeholders in expected_output.txt files — {{DateTime}}, {{GUID}}, {{Integer}} etc.

black-box date dynamic expected guid non-deterministic output placeholder template timestamp

output placeholder
dynamic test output
test placeholder

stdout.println("Request ID: " + requestId.toString())

Explanation: GUID has no toString() method — use the $ prefix operator for String conversion. See ek9 -h E50060 for details.


Question: How do coverage thresholds and exit codes work in EK9?

What happens if my EK9 test coverage is below 80%?

How does EK9 enforce minimum code coverage?

Answer:
EK9 enforces an 80% coverage threshold as a quality gate. Test exit codes distinguish between test failures and coverage failures.

EXIT CODES
The test runner returns specific exit codes:
  0   All tests passed AND coverage >= 80%
  11  One or more tests failed
  12  All tests passed BUT coverage < 80%

This means a CI/CD pipeline can distinguish between:
- Everything good (0)
- Broken code (11) — fix the failing tests first
- Insufficient testing (12) — add more tests

THE 80% THRESHOLD
EK9 enforces 80% line coverage by default. This is not configurable per-project — it is a language-level quality gate. If your tests pass but only cover 60% of the code, the build fails with exit code 12.

WHY 80%
Industry research shows 80% is the sweet spot:
- Below 80%: significant untested paths likely contain bugs
- Above 80%: diminishing returns, risk of gaming metrics
- 100%: usually not worth the effort for the last 20%

COVERAGE MODES
Three instrumentation modes:
  SET     records whether each line was executed (default, lowest overhead)
  COUNT   counts how many times each line ran (identifies hot paths)
  ATOMIC  thread-safe counting for concurrent tests

VIEWING COVERAGE
  ek9 -t4 myApp.ek9    JSON output with coverage data
  ek9 -t5 myApp.ek9    verbose coverage breakdown per function
  ek9 -t6 myApp.ek9    interactive HTML dashboard with source highlighting

The HTML dashboard (-t6) shows coverage per function, complexity badges, and readability scores.

CI/CD INTEGRATION
  ek9 -t2 myApp.ek9    JSON for programmatic parsing
  ek9 -t3 myApp.ek9    JUnit XML for CI dashboards (Jenkins, GitLab)
  echo $?              check exit code: 0=pass, 11=fail, 12=low coverage

See Q206 for coverage basics. See Q207 for output formats. See Q804 for why built-in testing.

defines module qa.testdeep.coverage.thresholds

  defines function

    classify() as pure
      -> n as Integer
      <- rtn as String?

      if n > 0
        rtn: "positive"
      else if n < 0
        rtn: "negative"
      else
        rtn: "zero"

  defines program

    // === COVERAGE THRESHOLD DEMO ===
    // Tests must cover >= 80% of code to pass.
    // Exit code 0  = pass + coverage OK
    // Exit code 11 = test failure
    // Exit code 12 = tests pass but coverage < 80%

    @Test
    ClassifyPositiveTest()
      result <- classify(5)
      assert result == "positive"

    @Test
    ClassifyNegativeTest()
      result <- classify(-3)
      assert result == "negative"

    @Test
    ClassifyZeroTest()
      result <- classify(0)
      assert result == "zero"

    // All three branches covered = good coverage.
    // Missing any branch risks exit code 12.

Java: JaCoCo with Maven enforcer plugin, configurable thresholds per package. Python: coverage.py with --fail-under=80. Rust: cargo-tarpaulin with --fail-under 80. Go: go test -cover shows percentage but no threshold enforcement built in. EK9: 80% threshold enforced by the compiler, exit code 12 for insufficient coverage, no configuration needed.

ci code coverage eighty enforce exit gate percent pipeline threshold

coverage threshold
test exit codes
80 percent coverage


Question: What are common stream pipeline errors in EK9?

Why does my stream pipeline fail to compile in EK9?

How do I fix stream head/tail/skip errors in EK9?

What are the rules for stream limiting operators in EK9?

Answer:
EK9 stream pipelines validate function signatures and types at compile time. Common errors involve head/tail/skip arguments, filter predicates, and type mismatches.

HEAD, TAIL, SKIP ARGUMENTS
These limiting operators accept:
- No argument (defaults to 1): cat items | head > result
- Integer literal (must be > 0): cat items | head 5 > result
- Integer variable: cat items | head with limit > result
- Supplier function (no params, returns Integer): cat items | head JustOne > result

Using zero, negative, non-integer, or functions with parameters triggers compile errors.

FILTER AND SPLIT
Filter requires a predicate (returns Boolean):
  cat items | filter by isPositive > result
The function must take one parameter matching the stream type and return Boolean.

CALL AND ASYNC
Stream call/async execute function delegates flowing through the pipeline:
  cat [getSteve, getLimb] | call > collector
Functions must be zero-argument suppliers returning a value. Non-function types, void functions, and functions requiring parameters are all compile errors.

See Q126 for complete stream reference. See Q204 for black-box tests.

defines module qa.streams.pipeline.errors

  defines function

    JustOne()
      <- rtn <- 1

    JustDate()
      <- rtn <- 2024-10-01

    AcceptsArgument()
      -> arg0 as Integer
      <- rtn as Integer: arg0

    isShort() as pure
      -> item as String
      <- rtn as Boolean: item.length() < SHORT_THRESHOLD

  defines constant
    SHORT_THRESHOLD <- 5

  defines program

    StreamHeadDemo()
      stdout <- Stdout()

      // === head with no argument (defaults to 1) ===
      cat ["alpha", "beta", "gamma"]
        | head
        > stdout

      // === head with integer literal ===
      cat ["alpha", "beta", "gamma"]
        | head 2
        > stdout

      // === head with supplier function (reference) ===
      cat ["alpha", "beta", "gamma"]
        | head JustOne
        > stdout

      // === head with supplier function (call) ===
      cat ["alpha", "beta", "gamma"]
        | head JustOne()
        > stdout

      // === filter with predicate ===
      cat ["hi", "hello", "hey", "greetings"]
        | filter by isShort
        > stdout

Java: Stream.limit(n) and skip(n) accept long — zero and negative accepted at compile time, fail at runtime. Python: itertools.islice accepts any integer silently. Rust: .take(n) and .skip(n) accept usize with no value validation. EK9: compile-time validation of positive integers, correct function signatures, and type compatibility throughout the pipeline.

async call error filter head limit pipeline skip stream tail

stream errors
stream head tail skip
stream pipeline mistakes

Explanation: Stream head/tail/skip supplier functions must take no parameters. AcceptsArgument requires an Integer argument so cannot be used as a count supplier. See ek9 -h E07450 for details.

Explanation: Stream head/tail/skip function call must return Integer. JustDate returns a Date which cannot provide a count. See ek9 -h E07870 for details.


Question: How do call and async work in EK9 stream pipelines?

How do I execute function delegates in an EK9 stream?

What is the difference between call and async in EK9 streams?

Why does my stream call fail with TYPE_MUST_BE_FUNCTION?

Answer:
EK9 stream pipelines support call (sequential) and async (concurrent) operators to execute function delegates flowing through the pipeline.

CALL AND ASYNC
The call and async operators execute functions that are items in the stream:
  cat [getSteve, getLimb] | call > collector
The stream must contain functions. call executes each sequentially; async executes them concurrently on separate threads.

FUNCTION REQUIREMENTS
Functions used with call/async must:
1. Be actual function delegates (not integers, strings, or class instances)
2. Take no parameters (zero-argument suppliers)
3. Return a value (the return becomes the next stream element)

COMMON MISTAKES
- Streaming non-function values: cat [1, 2] | call triggers E04040 TYPE_MUST_BE_FUNCTION
- Using void functions: cat [noReturn] | call triggers E07490 FUNCTION_MUST_RETURN_VALUE
- Using functions with parameters: cat [needsArg] | call triggers E06310 REQUIRE_NO_ARGUMENTS
- Passing a function TO call: cat items | call myFunc triggers E07880 FUNCTION_OR_DELEGATE_NOT_REQUIRED

DYNAMIC FUNCTIONS
If a function needs captured data, use a dynamic function:
  supplier <- () is AbstractFn as function (rtn: capturedValue)
  cat [supplier] | call > result

See Q812 for stream head/tail/skip errors. See Q126 for complete stream reference.

defines module qa.streams.call.async.rules

  defines function

    getGreeting()
      <- rtn <- "Hello"

    getWorld()
      <- rtn <- "World"

  defines program

    StreamFunctionDemo()
      stdout <- Stdout()

      // These zero-arg functions return String values.
      // They are suitable for use with call/async in streams:
      //   cat [getGreeting, getWorld] | call > stdout

      greeting <- getGreeting()
      stdout.println(greeting)

      world <- getWorld()
      stdout.println(world)

Java: No built-in stream call/async. Use map(Supplier::get) or CompletableFuture.supplyAsync(). Python: map(func, iterable) for sequential, asyncio.gather() for parallel. Rust: .map(|f| f()) in iterators. Go: goroutines with channels. EK9: call and async are native pipeline operators with compile-time function signature validation.

async call concurrent delegate execute function parallel pipeline stream supplier

stream call async
function execution in stream


Question: How does EK9 enforce nesting depth limits?

What is the maximum nesting depth in EK9?

Why does my code fail with EXCESSIVE_NESTING?

How do I fix E11011 excessive nesting in EK9?

Answer:
EK9 enforces a maximum nesting depth of 6 levels. Code nested deeper than 6 if/while/for/switch blocks triggers E11011 EXCESSIVE_NESTING at compile time.

THRESHOLD
The nesting depth threshold is 6 levels. Each control flow construct (if, while, for, switch, try) adds one level of nesting. At 7 levels, the compiler rejects the code.

WHY THIS MATTERS
Deeply nested code is hard to follow, hard to test, and correlates with bugs. Microsoft Research found that functions with nesting depth > 5 have 3x the defect rate of shallow functions.

HOW TO FIX
- Extract inner logic to separate functions
- Use guard clauses to exit early
- Use switch instead of nested if/else chains
- Consider polymorphism for type-based branching

NESTING VS COGNITIVE COMPLEXITY
Nesting depth (E11011, threshold 6) measures structural depth. Cognitive complexity (E11021, threshold 35) measures mental effort. Deep-but-simple code triggers nesting first. Shallow-but-complex code triggers cognitive first.

See Q310 for code quality overview. See Q312 for complexity metrics.

defines module qa.quality.nesting.depth

  defines function

    // === AT THE BOUNDARY: 6 levels (maximum allowed) ===
    classifyDepth() as pure
      -> inputValue as Integer
      <- result as Integer: 0

      levelTwo <- 2
      levelThree <- 3
      levelFour <- 4
      levelFive <- 5

      if inputValue > 0
        if inputValue > 1
          if inputValue > levelTwo
            if inputValue > levelThree
              if inputValue > levelFour
                if inputValue > levelFive
                  result: 6

  defines program

    NestingDepthDemo()
      stdout <- Stdout()

      result <- classifyDepth(7)
      stdout.println("Depth classification: " + $result)

      shallow <- classifyDepth(1)
      stdout.println("Shallow: " + $shallow)

Java: SonarQube flags deep nesting as a code smell but it compiles fine. Rust: clippy warns but is suppressible. Go: cultural convention only. C#: Roslyn analyzers warn but don't block. EK9: nesting depth > 6 is a hard compiler error that cannot be bypassed.

E11011 complexity depth extract guard nesting quality refactor threshold

                if inputValue > levelFive
                  result: 6

                if inputValue > levelFive
                  if inputValue > 6
                    result: 7

Explanation: Adding a 7th nesting level exceeds the maximum depth of 6. Extract the inner logic to a separate function or use guard clauses. See ek9 -h E11011 for details.


Question: Why is calling a computational operator without using its result an error in EK9?

What is E11050 DISCARDED_OPERATOR_RETURN?

Why can't I call an operator as a bare statement in EK9?

How do I fix discarded return value errors in EK9?

Answer:
EK9 detects when a computational operator is called explicitly but its return value is discarded. This is dead code — the computation achieves nothing.

WHAT TRIGGERS E11050
Calling an operator via method syntax and ignoring the result:
  a._eq(b)     // computes equality but throws away the Boolean
  a.+(b)       // computes addition but throws away the result
These statements achieve nothing — the result is computed and discarded.

MUTATING VS COMPUTATIONAL
Mutating operators return Void and are valid as statements:
  a += b       // modifies a in place, no return to discard
  counter++    // mutates counter, Void return
Computational operators return values that must be used:
  isEqual <- a == b    // use the result
  sum <- a + b         // capture the value

HOW TO FIX
- Assign the result: isEqual <- a._eq(b)
- Use in an expression: if a == b
- If you want mutation, use the mutating operator: a += b

See Q310 for code quality overview. See Q316 for operator semantics.

defines module qa.quality.discarded.return

  defines program

    DiscardedReturnDemo()
      -> greeting as String
      stdout <- Stdout()

      farewell <- "goodbye"

      // === CORRECT: capture the explicit operator call result ===
      isEqual <- greeting.==(farewell)
      stdout.println(`Equal: ${isEqual}`)

      // === CORRECT: use in an expression ===
      if greeting == farewell
        stdout.println("same")
      else
        stdout.println("different")

      // === CORRECT: explicit operator call with captured result ===
      price <- 100
      tax <- 15
      sum <- price.+(tax)
      require sum?

      // === CORRECT: mutating operators are fine as statements ===
      counter <- 0
      counter++
      stdout.println("Counter: " + $counter)

Java: silently discards operator results in expression statements. Python: same — no warning for unused computation. Rust: warns about unused Result but not operators. Go: requires using all return values. EK9: makes discarded computational operator returns a compile error.

E11050 code computational dead discarded mutation operator quality return

discarded return
E11050
operator return value

      sum <- price.+(tax)
      require sum?

Explanation: Calling the + operator via explicit method syntax and discarding the result means the addition achieves nothing. Assign the result or use the mutating operator +=. See ek9 -h E11050 for details.


Question: Why does EK9 require named arguments for multiple Boolean parameters?

What is E11061 BOOLEAN_ARGUMENTS_REQUIRE_NAMES?

Why can't I pass two Boolean literals positionally in EK9?

How do I fix Boolean argument naming errors in EK9?

Answer:
EK9 requires named arguments when a call passes 2 or more Boolean literals. This prevents the 'Boolean blindness' anti-pattern.

THE PROBLEM
Positional Boolean arguments are unreadable at the call site:
  connect('db', 5432, true, false)   // what do true and false mean?
Without checking the function signature, the reader has no idea what each Boolean controls.

THE RULE
One Boolean literal is fine — context usually makes it clear:
  setVisible(true)                   // obviously sets visibility
Two or more Boolean literals trigger E11061:
  connect('db', 5432, true, false)   // ERROR: ambiguous

THE FIX
Use named arguments (EK9 requires all-or-nothing naming):
  connect(host: 'db', port: 5432, useSsl: true, autoReconnect: false)

See Q310 for code quality overview. See Q694 for named arguments.

defines module qa.quality.boolean.args

  defines function

    configureNetwork()
      ->
        host as String
        useSsl as Boolean
        autoReconnect as Boolean
      stdout <- Stdout()
      stdout.println(`Host: ${host} SSL: ${useSsl} Reconnect: ${autoReconnect}`)

  defines program

    BooleanArgsDemo()
      stdout <- Stdout()

      // === CORRECT: named arguments for 2+ Booleans ===
      configureNetwork(host: "db.example.com", useSsl: true, autoReconnect: false)

      // === CORRECT: single Boolean is fine positionally ===
      stdout.println("Single Boolean: " + $true)

Java: no enforcement — Boolean parameters are a known code smell. Python: no enforcement but PEP 8 recommends keyword args. Swift: requires argument labels by default. Kotlin: supports named args but doesn't require them. EK9: 2+ Boolean literals = compile error unless named.

E11061 ambiguous arguments boolean named parameters quality readability

boolean arguments
named arguments
E11061

Explanation: Two Boolean literals (true, false) as positional arguments are ambiguous. Use named arguments so the reader knows what each Boolean controls. See ek9 -h E11061 for details.


Question: Why does EK9 require named arguments for 4+ parameters?

What is E11062 MANY_ARGUMENTS_REQUIRE_NAMES?

Why can't I pass 4 positional arguments in EK9?

How do I fix many arguments need names in EK9?

Answer:
EK9 requires named arguments when a call passes 4 or more positional arguments. Beyond 3 arguments, the 3rd and 4th are routinely confused.

THE RULE
3 or fewer positional arguments are fine:
  connect(host, port, timeout)       // OK: 3 args
4 or more trigger E11062:
  connect(host, port, timeout, retries)  // ERROR: use named

THE FIX
Use named arguments (EK9 requires all-or-nothing naming):
  connect(host: host, port: port, timeout: timeout, retries: retries)

WHY THIS MATTERS
Miller's Law: humans can track 7 +/- 2 items. At 4+ parameters, positional confusion causes bugs that are invisible at the call site. Named arguments make every parameter's purpose visible.

See Q694 for named argument patterns. See Q816 for Boolean argument naming.

defines module qa.quality.many.args

  defines function

    scheduleJob()
      ->
        jobName as String
        path as String
        retries as Integer
        interval as Integer
      stdout <- Stdout()
      stdout.println(`Scheduled ${jobName} at ${path} retries=${retries} interval=${interval}`)

  defines program

    ManyArgsDemo()
      stdout <- Stdout()

      // === CORRECT: named arguments for 4+ parameters ===
      scheduleJob(jobName: "backup", path: "/data", retries: 3, interval: 60)

      // === CORRECT: 3 or fewer positional is fine ===
      stdout.println("Three args or fewer: no naming required")

Java: no enforcement. Python: PEP 8 recommends keyword args for clarity. Swift: requires argument labels by default. Kotlin: supports named args. EK9: 4+ positional args = compile error.

E11062 arguments many named parameters positional quality readability

Explanation: 4 positional arguments exceed the readability threshold. Use named arguments so each parameter's purpose is clear at the call site. See ek9 -h E11062 for details.


Question: Why does EK9 reject self-cancelling arithmetic?

Why can't I write x - x or x / x in EK9?

How does EK9 detect tautological arithmetic?

Answer:
EK9 detects self-cancelling arithmetic expressions at compile time. Operations like x - x, x / x, x mod 1, and x rem 1 always produce a constant result regardless of x, making them dead code or logic errors.

DETECTED PATTERNS
  amount - amount      // always 0
  amount / amount      // always 1 (or division by zero if 0)
  amount mod amount    // always 0
  amount rem amount    // always 0
  amount mod 1         // always 0
  amount rem 1         // always 0

WHY THIS MATTERS
These patterns usually indicate copy-paste errors where the developer meant to use a different variable on one side. The compiler catches the mistake immediately.

HOW TO FIX
- Check if you meant a different variable on one side
- If you genuinely want 0, assign it directly: result: 0
- If you genuinely want 1, assign it directly: result: 1

See Q310 for code quality overview. See Q734 for other tautology detection.

defines module qa.quality.constant.arithmetic

  defines function

    calculateRemaining() as pure
      ->
        amount as Integer
        deduction as Integer
      <- result as Integer?

      result: amount - deduction

  defines program

    ConstantArithmeticDemo()
      stdout <- Stdout()

      remaining <- calculateRemaining(100, 25)
      stdout.println(`Remaining: ${remaining}`)

Java: no detection — self-cancelling arithmetic compiles silently. Python: no detection. Rust: clippy warns about some patterns. Go: no detection. EK9: compile-time error for all self-cancelling arithmetic.

E08083 arithmetic code constant dead quality self-cancelling tautology

constant arithmetic
tautological expression
E08083

Explanation: Subtracting a variable from itself always produces 0. This is usually a copy-paste error where you meant a different variable. See ek9 -h E08083 for details.


Question: Why does EK9 reject duplicate property fields in records?

What is E02010 DUPLICATE_PROPERTY_FIELD?

Why can't I redeclare a parent field in a child record?

How does EK9 prevent property shadowing?

Answer:
EK9 prevents child records from redeclaring properties that already exist in the parent. Property shadowing creates confusion about which field is being accessed.

THE RULE
A child record cannot have a property with the same name as any inherited property:
  BaseRecord
    name as String
  ChildRecord extends BaseRecord
    name as String    // ERROR: E02010 duplicate property

WHY THIS MATTERS
Property shadowing in Java causes subtle bugs: super.name and this.name refer to different fields. Code that accesses the field through the base type gets the wrong value. EK9 eliminates this by rejecting the duplicate at compile time.

HOW TO FIX
- Use a different name in the child: childName as String
- If you need to override behaviour, use a method instead

See Q97 for class vs record guidance. See Q96 for inheritance.

defines module qa.quality.duplicate.property

  defines record

    BaseRecord as open
      label as String: String()
      default operator ?

    ChildRecord extends BaseRecord
      childLabel as String: String()
      default operator ?

  defines program

    DuplicatePropertyDemo()
      stdout <- Stdout()

      child <- ChildRecord()
      stdout.println(`Label: ${child.label}`)
      stdout.println(`Child label: ${child.childLabel}`)

Java: allows field shadowing silently — a major source of bugs. Python: instance attributes shadow freely. Rust: no inheritance, so not applicable. Go: embedded structs can shadow. EK9: duplicate property field is a compile error.

E02010 duplicate field inheritance property quality record shadow

duplicate property
field shadowing
E02010

    ChildRecord extends BaseRecord
      label as String: String()

Explanation: The parent BaseRecord already has a property named 'label'. Redeclaring it in the child creates a duplicate. Use a different name. See ek9 -h E02010 for details.


Question: Why does EK9 reject redundant empty checks on collections?

Why does EK9 warn about checking if a new list is empty?

Answer:
EK9 detects redundant empty checks using data flow analysis. A freshly created collection is always empty, so checking 'if items empty' immediately after creation is redundant — the condition is always true.

E08092: REDUNDANT EMPTY CHECK
Checking 'empty' on a provably empty collection:
  items <- List() of String
  if items empty           // ALWAYS true — just created
    result: "empty"        // this always executes

E08093: NEVER EMPTY CHECK
Checking 'not empty' on a provably empty collection:
  items <- List() of String
  if items not empty       // ALWAYS false — just created
    result: "has items"    // this never executes (dead code)

WHEN CHECKS ARE VALID
After items have been added or the collection comes from a parameter:
  items <- List() of String
  items += "hello"
  if items empty           // valid — state changed since creation
    result: "empty"

See Q310 for code quality overview. See Q734 for tautology detection.

defines module qa.quality.empty.check

  defines function

    checkCollection()
      -> greeting as String
      <- result as String: String()

      items <- List() of String
      items += greeting
      if items empty
        result: "empty"
      else
        result: "has items"

  defines program

    EmptyCheckDemo()
      stdout <- Stdout()

      result <- checkCollection("hello")
      stdout.println(`Collection: ${result}`)

Java: no detection — redundant isEmpty() compiles silently. Python: no detection. Rust: no detection. Go: no detection. EK9: compile-time error for provably redundant or impossible empty checks.

E08092 E08093 code collection dead dict empty list redundant tautology

      items += greeting
      items := List() of String

Explanation: Reassigning items to a freshly-created empty list immediately before 'if items empty' makes the check provably always true, so the compiler flags a redundant empty check. See ek9 -h E08092 for details.


Question: Why can't I construct an abstract type directly in EK9?

What is E10030 CONSTRUCTOR_USED_ON_ABSTRACT_TYPE?

Why does EK9 reject new MyAbstractClass()?

How do I fix abstract type construction errors in EK9?

Answer:
EK9 prevents direct construction of abstract types at compile time. Abstract types define contracts — they must be extended with concrete implementations.

THE RULE
You cannot call the constructor of an abstract class, function, or generic parameterized with an abstract type:
  Shape as abstract    // abstract — no direct construction
  s <- Shape()         // ERROR: E10030

HOW TO FIX
- Create a concrete subclass and construct that instead
- Use a dynamic class/function for inline implementation
- If using generics, parameterize with a concrete type

WHY THIS MATTERS
Abstract types may have abstract methods with no implementation. Constructing them would create an object that cannot respond to all its methods. EK9 catches this at compile time rather than allowing it to fail at runtime.

See Q97 for class vs record. See Q96 for inheritance.

defines module qa.quality.abstract.construction

  defines class

    Shape as abstract
      default operator ?

    Circle extends Shape
      default Circle()
      default operator ?

  defines program

    AbstractConstructionDemo()
      stdout <- Stdout()

      // === CORRECT: construct concrete subclass ===
      shape <- Circle()
      stdout.println(`Shape created: ${shape?}`)

Java: cannot instantiate abstract class — runtime InstantiationError if attempted via reflection. Python: ABCMeta raises TypeError at runtime. Rust: traits cannot be constructed (no inheritance). Go: interfaces have no constructors. EK9: compile-time error for abstract construction.

E10030 abstract concrete construction constructor implementation type

Explanation: Shape is abstract and cannot be constructed directly. Use a concrete subclass like Circle instead. See ek9 -h E50080 for details.


Question: When can I use super in EK9?

What is E05080 INAPPROPRIATE_USE_OF_SUPER?

Answer:
EK9 restricts the use of 'super' to specific contexts within subclass methods. Using super as a standalone expression, assigning it to a variable, or passing it as an argument is not allowed.

VALID USES OF SUPER
Super is valid only for calling parent methods:
  super.methodName()         // call parent implementation

INVALID USES OF SUPER
  theSuper <- super          // ERROR: can't assign super to variable
  theSuper := super          // ERROR: can't reassign to super
  this.callMethod(super)     // ERROR: can't pass super as argument

WHY THIS RESTRICTION
Super is not an object reference — it is a dispatch mechanism. Allowing super as a value would create aliasing problems and break the inheritance contract. EK9 enforces that super is only used for method dispatch.

See Q96 for inheritance. See Q97 for class vs record.

defines module qa.quality.super.misuse

  defines class

    BaseWidget as open
      getName() as pure
        <- rtn as String: "BaseWidget"
      default operator ?

    DerivedWidget extends BaseWidget
      override getName() as pure
        <- rtn as String: "DerivedWidget"

      displayParentName()
        stdout <- Stdout()
        // === CORRECT: super for method dispatch ===
        name <- super.getName()
        stdout.println(`Parent: ${name}`)

  defines program

    SuperMisuseDemo()
      widget <- DerivedWidget()
      widget.displayParentName()

Java: super can be used for method calls and constructor chaining but not as a standalone value. Python: super() returns a proxy object. Rust: no super — use trait default methods. Go: embedded struct accessed by name. EK9: super restricted to method dispatch only.

E05080 class dispatch inheritance method parent super

super keyword
E05080
inappropriate use of super

Explanation: Super cannot be used as a standalone expression or converted to a string. It is only valid for dispatching method calls to the parent class. See ek9 -h E05080 for details.


Question: Why must certain operators return specific types in EK9?

What is E07410 MUST_RETURN_SAME_AS_CONSTRUCT_TYPE?

Why does my operator ~ return the wrong type?

What are operator return type rules in EK9?

Answer:
EK9 enforces strict return type rules for operators. Some operators must return the same type as the construct they belong to.

OPERATOR RETURN TYPE RULES
The ~ (negate/complement) operator must return the same type as the class:
  MyNumber
    operator ~ as pure
      <- rtn as MyNumber   // CORRECT: returns same type

Returning a different type triggers E07410:
  MyNumber
    operator ~ as pure
      <- rtn as Float      // ERROR: must return MyNumber

OTHER OPERATOR RULES
- Comparison operators (==, <>, <, >) must return Boolean
- The <=> (spaceship) operator must return Integer
- The #? (hashcode) operator must return Integer
- The $ (string) operator must return String
- The ~ (negate) operator must return the same construct type

See Q316 for operator semantics. See Q310 for code quality.

defines module qa.quality.operator.return

  defines class

    Temperature
      degrees as Float: 0.0

      Temperature() as pure
        -> initialDegrees as Float
        this.degrees :=: initialDegrees

      operator ~ as pure
        <- rtn as Temperature?
        negated <- 0.0 - degrees
        rtn: Temperature(negated)

      operator $ as pure
        <- rtn as String: $degrees

      default operator ?

  defines program

    OperatorReturnDemo()
      stdout <- Stdout()

      warm <- Temperature(25.0)
      cold <- ~warm
      stdout.println(`Warm: ${warm}`)
      stdout.println(`Negated: ${cold}`)

Java: operator overloading not supported (except + for String). C++: operator overloading has no return type enforcement. Rust: trait-based operators enforce return types via associated types. Python: __neg__ can return any type. EK9: compile-time return type enforcement for all operators.

E07410 complement construct negate operator return type

      operator ~ as pure
        <- rtn as Float?
        negated <- 0.0 - degrees
        rtn: negated

Explanation: The ~ operator must return the same type as the class it belongs to; Temperature's ~ must return Temperature, not Float. See ek9 -h E07410 for details.


Question: Why must stream functions have exactly one parameter in EK9?

What is E07460 FUNCTION_MUST_HAVE_SINGLE_PARAMETER?

Why does my stream filter function fail with parameter count error?

How many parameters can a stream function have in EK9?

Answer:
EK9 stream operators like filter, uniq, sort, and map require functions with exactly one parameter matching the stream element type.

THE RULE
Stream pipeline functions must have a single parameter:
  isPositive() as pure
    -> amount as Integer       // ONE parameter: correct
    <- rtn as Boolean: amount > 0

A function with zero or multiple parameters triggers E07460:
  isInRange() as pure
    -> lo as Integer           // TWO parameters: wrong
    -> hi as Integer
    <- rtn as Boolean: true

WHY THIS RULE
Stream operators call the function once per element, passing the current element as the single argument. A function with 0 or 2+ parameters cannot receive a single stream element.

HOW TO FIX
- Use a single parameter matching the stream type
- If you need additional context, use a dynamic function that captures extra values

See Q812 for stream errors. See Q126 for stream reference.

defines module qa.streams.function.arity

  defines function

    isPositive() as pure
      -> amount as Integer
      <- rtn as Boolean: amount > 0

    isInRange() as pure
      ->
        lowerBound as Integer
        upperBound as Integer
      <- rtn as Boolean: lowerBound < upperBound

  defines program

    StreamFunctionArityDemo()
      stdout <- Stdout()

      // === CORRECT: filter with single-parameter predicate ===
      cat [3, -1, 5, -2, 8]
        | filter by isPositive
        > stdout

Java: Stream.filter() takes Predicate<T> (single arg). Python: filter(func, iterable) passes one item. Rust: .filter(|x| ...) takes one reference. Go: no built-in stream, but range gives one item. EK9: compile-time enforcement that stream functions have exactly one parameter.

E07460 arity filter function parameter single sort stream uniq

Explanation: Stream uniq requires a function with exactly one parameter matching the stream type. isInRange has two parameters so cannot receive a single stream element. See ek9 -h E07460 for details.


Question: Why does EK9 reject 'not empty' on a freshly created collection?

Why is my 'not empty' check dead code in EK9?

How does EK9 detect impossible empty checks?

Answer:
EK9 detects when 'not empty' is checked on a collection that is provably empty. The condition is always false, making the body dead code.

THE PATTERN
  items <- List() of String
  if items not empty       // ALWAYS false — list just created
    process(items)         // dead code — never executes

The compiler knows the list was just created with no items added, so 'not empty' can never be true at this point.

VALID USE
After items have been added, the check is meaningful:
  items <- List() of String
  items += value
  if items not empty       // valid — items were added
    process(items)

See Q820 for redundant empty checks (E08092). See Q734 for tautology detection.

defines module qa.quality.never.empty

  defines function

    processIfPresent()
      -> inputValue as String
      <- result as String: "nothing"

      items <- List() of String
      items += inputValue
      if items not empty
        result: "has items"

  defines program

    NeverEmptyDemo()
      stdout <- Stdout()
      result <- processIfPresent("hello")
      stdout.println(`Result: ${result}`)

Java: no detection. Python: no detection. Rust: no detection. Go: no detection. EK9: compile-time error for provably impossible 'not empty' checks using data flow analysis.

E08093 code collection dead dict empty list never tautology

      items += inputValue
      if items not empty

      //items += inputValue
      if items not empty

Explanation: Without adding items, the list is provably empty. 'not empty' is always false — the body is dead code. See ek9 -h E08093 for details.


Question: Why does EK9 reject override on constructs that cannot override?

Why can't I use override on a program in EK9?

Where is the override keyword not allowed in EK9?

Answer:
EK9 rejects the override keyword on constructs that do not support inheritance-based overriding. Programs and service methods cannot override anything.

WHERE OVERRIDE IS INVALID
- Programs: programs are entry points, not extendable
- Service methods: services do not inherit from other services

WHERE OVERRIDE IS VALID
- Class methods that replace a parent class method
- Trait method implementations in classes
- Function extensions that replace a parent function

WHY THIS MATTERS
The override keyword is a contract: 'I intend to replace a parent implementation.' Using it on a construct that has no parent hierarchy is a logic error caught at compile time.

See Q96 for inheritance. See Q155 for testing.

defines module qa.quality.override.check

  defines program

    OverrideCheckDemo()
      stdout <- Stdout()
      stdout.println("Programs cannot use override")

Java: @Override on a static method or constructor is a compile error. Kotlin: override keyword validated against parent. C++: override specifier checked. Python: no enforcement. EK9: override validated at SYMBOL_DEFINITION phase.

E05100 inappropriate inheritance override program service

Explanation: Programs cannot use override — they are entry points with no parent to override. Remove the override keyword. See ek9 -h E05100 for details.


Question: Why does EK9 reject the ? modifier on function parameters?

What is E07300 DECLARED_AS_NULL_NOT_NEEDED?

Why can't I declare a parameter as potentially unset in EK9?

How does EK9 enforce parameter initialisation?

Answer:
EK9 requires function parameters to always be initialised. Declaring a parameter with ? (potentially unset) is not needed because the caller must always provide a value.

THE RULE
Parameters cannot use the ? unset modifier:
  process()
    -> input as String?    // ERROR: E07300
Parameters are always set by the caller. The ? modifier would be misleading.

THE FIX
Remove the ? modifier:
  process()
    -> input as String     // CORRECT: always initialised

WHY THIS MATTERS
EK9 pushes for everything to be initialised. Parameters are provided by callers, so they are always set when the function body executes. Allowing ? on parameters would create false optionality.

See Q310 for code quality. See Q126 for function patterns.

defines module qa.quality.null.not.needed

  defines function

    processInput() as pure
      -> input as String
      <- rtn as String: `Processed: ${input}`

  defines program

    NullNotNeededDemo()
      stdout <- Stdout()
      result <- processInput("hello")
      stdout.println(result)

Java: parameters are always non-null unless explicitly @Nullable. Kotlin: parameters can be nullable with ?. Rust: parameters are always initialised. Go: parameters always have zero values. EK9: parameters are always initialised — ? modifier not allowed.

E07300 declared initialised null optional parameter unset

Explanation: Function parameters are always provided by the caller and cannot be unset. Remove the ? modifier. See ek9 -h E07300 for details.


Question: Why can't I use sanitized at a call site in EK9?

What is E07942 SANITIZED_NOT_ALLOWED_AT_CALL_SITE?

Where does sanitized go — declaration or call site?

How do I fix sanitized at call site errors in EK9?

Answer:
EK9 enforces that the sanitized keyword is only used in parameter declarations, not at call sites. Sanitization is the callee's responsibility, not the caller's.

THE RULE
Sanitized goes on the parameter declaration:
  processInput()
    -> data as sanitized String    // CORRECT: callee declares sanitization
Not at the call site:
  processInput(sanitized userInput)  // ERROR: caller cannot specify

WHY THIS MATTERS
Sanitization is a contract between the function and its callers. The function declares that it sanitizes its input — this is enforced at compile time. If callers could specify sanitization, it would be ambiguous: does the caller sanitize, the callee, or both?

EK9's rule: the function that receives the data is responsible for declaring and performing sanitization. The caller just passes the data.

See Q127 for sanitized parameters. See Q310 for code quality.

defines module qa.quality.sanitized.callsite

  defines function

    processInput()
      -> rawInput as sanitized String
      <- rtn as String: rawInput

  defines program

    SanitizedCallSiteDemo()
      stdout <- Stdout()
      userInput <- "hello <script>alert('xss')</script>"

      // === CORRECT: just pass the data, callee sanitizes ===
      result <- processInput(userInput)
      stdout.println(result)

Java: no built-in sanitization — developers use @Valid, @Sanitize annotations. Python: no enforcement. Rust: no built-in concept. Go: no enforcement. EK9: sanitized keyword on parameter declarations enforced at compile time, rejected at call sites.

E07942 call input parameter sanitized security site validation

result <- processInput(sanitized userInput)

Explanation: The sanitized keyword belongs on the parameter declaration, not at the call site. The callee decides what to sanitize. See ek9 -h E07942 for details.


Question: Why does EK9 reject duplicate properties in records with JSON support?

What is E02020 CANNOT_SUPPORT_TO_JSON_DUPLICATE_PROPERTY_FIELD?

Why can't my child record redeclare a parent property with default operators?

How does property duplication affect JSON serialization in EK9?

Answer:
When a record uses 'default operator' to auto-generate operators including $$ (toJSON), EK9 requires all property names to be unique across the inheritance hierarchy. Duplicate property names would produce ambiguous JSON keys.

THE RULE
If a child record redeclares a parent property AND uses 'default operator':
  ParentRecord
    name as String
    default operator
  ChildRecord extends ParentRecord
    name as String       // ERROR: E02020 — ambiguous JSON key
    default operator

WHY THIS MATTERS
The $$ operator generates JSON like {"name": "value"}. If both parent and child have 'name', the JSON would have duplicate keys — which is undefined behaviour in JSON.

HOW TO FIX
- Use a different name in the child: childName as String
- Or manually implement the $$ operator instead of using default

See Q819 for duplicate property basics. See Q97 for records.

defines module qa.quality.duplicate.json

  defines record

    BaseConfig as open
      label as String: String()
      default operator ?
      default operator $

    ChildConfig extends BaseConfig
      childLabel as String: String()
      default operator ?
      default operator $

  defines program

    DuplicateJsonDemo()
      stdout <- Stdout()
      child <- ChildConfig()
      stdout.println($child)

Java: Jackson serializes duplicate fields from parent+child creating ambiguous JSON. Python: no enforcement. Rust: no inheritance. Go: embedded struct fields can shadow. EK9: compile-time error prevents ambiguous JSON serialization.

E02020 duplicate json property record serialization toJson

    ChildConfig extends BaseConfig
      label as String: String()

Explanation: The parent BaseConfig already has 'label'. Redeclaring it in a child creates a duplicate property. Use a different name. See ek9 -h E02010 for details.


Question: Why does EK9 reject non-aggregate types in certain contexts?

What is E04060 IS_NOT_AN_AGGREGATE_TYPE?

Why can't I use a function where a class is expected in EK9?

Answer:
EK9 distinguishes between aggregate types (class, record, component, trait, service) and non-aggregate types (function, program). Some operations require aggregate types.

AGGREGATE TYPES
Types that have properties, methods, and operators:
  class, record, component, trait, service, enumeration

NON-AGGREGATE TYPES
Callable units without properties:
  function, program

WHERE AGGREGATES ARE REQUIRED
- Stream sort: the items being sorted must be aggregate types with comparison operators
- Type extensions: extends requires an aggregate
- Property access: only aggregates have properties

COMMON MISTAKE
Passing a function reference where an aggregate instance is expected:
  cat [myFunction] | sort > result   // ERROR if sorting functions

See Q97 for class vs record. See Q126 for stream reference.

defines module qa.quality.aggregate.type

  defines function

    getAlpha()
      <- rtn <- "alpha"

  defines program

    AggregateTypeDemo()
      stdout <- Stdout()

      collection <- List() of String

      // === CORRECT: sort strings (aggregate type with <=> operator) ===
      cat ["alpha", "beta"] | sort > collection

      require collection?
      stdout.println("Sorted collection")

Java: everything is a class (functions are objects). Python: functions are objects with attributes. Rust: functions and closures are distinct from structs. Go: functions have no methods. EK9: clear separation — aggregates have properties and operators, functions are pure callables.

E04060 aggregate class function record sort stream type

Explanation: Stream sort requires aggregate types with comparison operators. Function references are not aggregates and cannot be sorted. See ek9 -h E04060 for details.


Question: Why can't I use an abstract function as a return type directly in EK9?

What is E50070 BAD_ABSTRACT_FUNCTION_USE?

Why does EK9 reject returning an abstract function type?

How do I fix abstract function return type errors?

Answer:
EK9 prevents using abstract function types directly as return types via built-in constructors like Function(). Abstract functions define contracts — they must be implemented concretely.

THE RULE
You cannot return an abstract function type via its constructor:
  getHandler()
    <- rtn as Function of (Integer, String): Function  // ERROR

HOW TO FIX
- Return a concrete implementation of the abstract function
- Use a dynamic function to create an inline implementation
- Declare the return type as the abstract function but assign a concrete one

WHY THIS MATTERS
Abstract functions have no implementation. Constructing one would create a callable with no body — which would fail at runtime. EK9 catches this at compile time.

See Q821 for abstract type construction. See Q126 for function patterns.

defines module qa.quality.abstract.function

  defines function

    abstractGreet() as abstract
      <- rtn as String?

    concreteGreet() is abstractGreet
      <- rtn <- "Hello from concrete"

    getGreeting()
      <- rtn as String: concreteGreet()

  defines program

    AbstractFunctionDemo()
      stdout <- Stdout()

      // === CORRECT: use concrete function ===
      greeting <- getGreeting()
      stdout.println(greeting)

Java: cannot instantiate abstract classes or interfaces directly. Python: ABCMeta raises TypeError. Rust: traits cannot be constructed. Go: interfaces have no constructors. EK9: compile-time error for abstract function construction.

E50070 abstract concrete function implementation return type

Explanation: Abstract functions cannot be called directly — they have no implementation. Use a concrete function that extends the abstract one. See ek9 -h E50080 for details.


Question: Why can't I have a local class with the same name as an imported reference in EK9?

What is E03010 CONSTRUCT_REFERENCE_CONFLICT?

Why does EK9 reject my class when I also import the same name?

How do I fix a name collision between a local construct and a referenced import?

Answer:
EK9 prevents any name collision between locally-defined constructs and imported references. Neither takes priority — the compiler rejects the ambiguity immediately with E03010.

THE RULE
If you reference (import) a symbol like 'Helper' from another module, you cannot also define a local class, function, or record called 'Helper' in the same module. The compiler cannot know which one you mean when you write 'Helper()'.

WHY NO PRIORITY RULES
Some languages let local definitions shadow imports (C#) or let wildcard imports shadow locals (Python's 'from module import *'). Both create subtle bugs where the wrong type is silently used. Java's 'import java.util.*' can silently resolve to the wrong List. EK9 forces you to choose explicitly.

HOW TO FIX
- Rename your local construct to a unique name
- Remove the reference if you only need the local version
- Use fully qualified module::TypeName if both are truly needed

THIS EXAMPLE
Shows proper module naming with distinct local class names. When importing from another module, always ensure your local constructs have unique names.

See Q741 for reference conflict overview. See Q119 for constructs overview. See Q13 for package vs module.

defines module qa.quality.reference.collision

  defines class

    <?-
      When importing from another module, always use distinct names.
      If another module exports 'Formatter', name your local version
      differently to avoid E03010 CONSTRUCT_REFERENCE_CONFLICT.
    -?>
    LocalFormatter
      prefix <- "["
      suffix <- "]"

      default LocalFormatter()

      LocalFormatter()
        ->
          p as String
          s as String
        prefix :=: p
        suffix :=: s

      formatText()
        -> text as String
        <- rtn as String: `${prefix}${text}${suffix}`

      default operator ?

  defines function

    <?-
      Similarly, local functions must not share names with imported references.
    -?>
    localTransform() as pure
      -> input as String
      <- rtn as String: `<${input}>`

  defines program

    ReferenceCollisionDemo()
      stdout <- Stdout()

      formatter <- LocalFormatter("[", "]")
      if formatter?
        stdout.println(formatter.formatText("hello"))

      stdout.println(localTransform("world"))

Java: wildcard imports ('import java.util.*') silently shadow local types — 'which List?' confusion surfaces late in production. Python: 'from module import *' can overwrite locals silently. C#: local types take priority over 'using' imports, masking bugs. Rust: 'use' aliases prevent collisions. EK9: any collision between local and imported is E03010, no shadowing permitted.

E03010 ambiguity collision conflict construct import local module reference shadow

Explanation: Renaming a function to the same name as an existing class creates a duplicate symbol. EK9 rejects any ambiguity between constructs sharing the same name. See ek9 -h E01030 for details.