How must constrained type constructors match the parameterising type?

← Generics · Ref: Q651

When a generic type is constrained with 'constrain by', the constraining type's constructors must be present on any type used to parameterise the generic. If the constraining type has a String constructor, any actual type argument must also have a String constructor.

CONSTRUCTOR MATCHING

If Shape has 'Shape(name as String)', then any T used in 'of type T constrain by Shape' must also have a String constructor.

WHY REQUIRED

Generic code may create instances of T using constructors from the constraining type. If the constructor does not exist, the code would fail at instantiation.

CORRECT PATTERN

Ensure subtypes mirror the constructor signatures:

  Shape as abstract
    Shape(n as String)
  Circle is Shape
    Circle(n as String)  // matches Shape's constructor
      super(n)

See Q195 for constrain by basics. See Q642 for constructor inference. See Q647 for function constraint limits.

Example

defines module qa.genericsdeep.constraintconstructormatch

  defines class

    <?-
      Abstract base class used as constraint.
      Has both default and String constructors.
    -?>
    Vehicle as abstract
      vehicleName as String: String()

      default Vehicle() as pure

      Vehicle() as pure
        -> n as String
        vehicleName :=? String(n)

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

      default operator ?

    <?-
      Concrete subtype with matching constructors.
      Both default and String constructors present (matching Vehicle).
    -?>
    Truck is Vehicle
      payload <- Integer()

      default Truck() as pure

      Truck() as pure
        -> n as String
        super(n)

      Truck() as pure
        ->
          n as String
          cap as Integer
        super(n)
        payload :=? cap

      override operator ? as pure
        <- rtn as Boolean: name()?

    <?-
      Subtype missing String constructor (for E06130 mutation testing).
    -?>
    Bicycle is Vehicle

      default Bicycle() as pure

      override operator ? as pure
        <- rtn as Boolean: name()?

    <?-
      Another concrete subtype with matching constructors.
    -?>
    Sedan is Vehicle
      doors as Integer: 4

      default Sedan() as pure

      Sedan() as pure
        -> n as String
        super(n)

      override operator ? as pure
        <- rtn as Boolean: name()?

  defines function

    <?-
      Constrained generic function.
      T must be a Vehicle, so T has name() method available.
    -?>
    describeVehicle() of type T constrain by Vehicle as open
      -> item as T
      <- rtn as String: item.name()

  defines program

    ConstraintConstructorDemo()
      stdout <- Stdout()

      truck <- Truck("Big Rig")
      truckDesc <- describeVehicle(truck)
      stdout.println(`Truck: ${truckDesc}`)

      sedan <- Sedan("Family Car")
      sedanDesc <- describeVehicle(sedan)
      stdout.println(`Sedan: ${sedanDesc}`)

Common mistakes

E50060 — When a generic type is constrained by Vehicle (which has a String constructor), any type used to parameterise it must also have a matching String constructor. Removing the String constructor from Truck while keeping only a non-matching constructor triggers E50060. See ek9 -h E50060 for details.

Incorrect:

Truck() as pure
        -> cap as Integer
        payload :=? cap

Correct:

Truck() as pure
        -> n as String
        super(n)

E06130 — Bicycle extends Vehicle but is missing the String constructor that Vehicle defines. When describeVehicle (constrained by Vehicle) is parameterised with Bicycle, the compiler detects the missing constructor match and triggers E06130. See ek9 -h E06130 for details.

Incorrect:

bicycle <- Bicycle()
      bicycleDesc <- describeVehicle(bicycle)
      stdout.println(`Bicycle: ${bicycleDesc}`)

Correct:

sedan <- Sedan("Family Car")
      sedanDesc <- describeVehicle(sedan)
      stdout.println(`Sedan: ${sedanDesc}`)
Other ways to ask this
  • What is E06130 constrained type constructor missing?
  • Why must constrained generic subtypes have matching constructors?
  • How do constructor requirements flow from constraints to subtypes?

Coming from another language?

Java: no constructor inheritance or matching requirement (erasure). C++: concepts can require constructibility. Rust: trait bounds define method requirements, not constructors. Go: no constructor concept. Kotlin: no constructor matching for generics. EK9: constraining type constructors must exist on parameterising type.

Keywords: match, parameter, constructor, E06130, constraint, constant, missing, type-parameter, generic, subtype