Why does E50040 fire on my abstract method in a class but not in a trait?

← Dependency Injection · Ref: Q1291

E50040 fires when an abstract method appears inside a construct that the compiler does not accept as an abstract container. The rule is NOT uniform across all constructs — it matters which kind of construct you are writing.

WHERE 'as abstract' IS REQUIRED ON THE CONTAINING CONSTRUCT

- Classes: a class with an abstract method must be declared 'Shape as abstract'
- Components: a component with an abstract method must be declared 'Logger as abstract'

Why: classes and components are instantiable by default. The compiler assumes you mean a concrete type unless you opt into abstractness. Leaving an abstract method inside a concrete container would produce an incomplete type with no way to construct it safely.

WHERE 'as abstract' IS NOT REQUIRED

- Traits: a trait is an inherently abstract interface-like construct. Abstract methods are the default expectation. You simply declare 'describe() as abstract' inside a trait and it compiles.
- Generic function declarations that happen to be abstract (E50040 is not the error code for those — a different error applies).

COMPARE THE TWO CASES DIRECTLY

  defines trait
    Printable                              // no 'as abstract' needed
      describe() as pure abstract          // OK — trait accepts abstract methods
        <- rtn as String?
  defines class
    Shape                                  // E50040 on area() below!
      area() as abstract                   // because 'Shape' is not 'as abstract'
        <- rtn as Float?

The trait compiles. The class does not — it reports E50040 on 'area'. Fixing the class means EITHER marking the container 'Shape as abstract' OR dropping 'as abstract' from the method and giving it a default implementation.

DI IMPLICATIONS

For dependency injection hierarchies, the idiomatic EK9 shape is:

- Declare the interface as a TRAIT (no 'as abstract' needed; all methods abstract by default)
- Declare an ABSTRACT CLASS or ABSTRACT COMPONENT that implements the trait and factors out shared state
- Declare CONCRETE subclasses or subcomponents that override the remaining abstract methods
- Register the concrete type in the application

This lets you express 'some shared implementation + some pluggable methods' without fighting E50040. If you find yourself reaching for 'as abstract' on a class or component, ask first whether the abstraction really belongs on a trait — traits are the lighter-weight vehicle for this kind of contract.

See Q1290 for the component-with-abstract-method variant. See Q103 for the full classes/methods/abstract story.

Example

defines module qa.dependencyinjection.e50040classvstrait

  defines trait

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

  defines class

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

    Circle extends Shape with trait of Printable
      radius as Float: Float()

      Circle()
        -> radius as Float
        this.radius :=: radius

      override area() as pure
        <- rtn as Float: 3.14159 * radius * radius

      override describe() as pure
        <- rtn as String: `Circle(r=${radius})`

      override operator ? as pure
        <- rtn as Boolean: radius?

  defines program

    E50040ClassVsTraitDemo()
      stdout <- Stdout()
      c <- Circle(5.0)
      stdout.println(c.describe())
      stdout.println(`area=${c.area()}`)

Common mistakes

E50040 — Declaring 'area() as abstract' inside 'defines class / Shape' without marking Shape itself 'as abstract' fires E50040. Unlike traits, classes are concrete by default and must opt into abstractness. Add 'as abstract' to the class header — 'Shape as abstract' — or convert the declaration to a trait if you only need an interface-like contract.

Incorrect:

    Shape

Correct:

    Shape as abstract
Other ways to ask this
  • When do I need to mark a construct 'as abstract' in EK9 — is it only classes and components?
  • Why does my trait compile with an abstract method but my class doesn't?
  • What's the difference between trait abstract methods and class abstract methods in EK9?
  • How do traits avoid the E50040 rule that classes and components are subject to?

Coming from another language?

Java: every interface method is implicitly abstract; abstract classes require the 'abstract' keyword on the class for abstract methods. EK9: traits behave like Java interfaces (methods implicitly abstract, no container modifier needed); classes and components behave more strictly than Java abstract classes (the container MUST be marked 'as abstract' if it contains any abstract methods). The distinction surfaces in E50040 — it fires on classes and components, never on traits.

Keywords: trait, component, E50040, dependency injection, abstract class, abstract method