How must constructor argument types match generic type parameters?

← Generics · Ref: Q644

When a generic type has an inferred constructor, the constructor parameter types must match the type parameters in both type and order. 'MyType of type (A, B)' must have constructor 'MyType(a as A, b as B)' with A first and B second.

ORDER MATTERS

For 'Pair of type (A, B)', the constructor must be:

  Pair(first as A, second as B)

Not 'Pair(first as B, second as A)' which reverses the types.

TYPE MATCHING

Each constructor parameter must use the corresponding type parameter:

  Position of type T
    Position(x as T)  // T matches the type parameter

WHY THIS RESTRICTION

The compiler uses the constructor to infer type arguments. If 'Pair("hello", 42)' is written, the compiler maps argument 1 to A (String) and argument 2 to B (Integer). The positional correspondence must be unambiguous.

See Q642 for constructor inference. See Q643 for two-constructor requirement. See Q645 for public constructor requirement.

Example

defines module qa.genericsdeep.constructorargtypes

  defines class

    <?-
      Single-param: constructor argument type matches type parameter T.
    -?>
    Wrapper of type T
      wrapped as T?

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

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

      unwrap()
        <- rtn as T: T()
        rtn :=? T(wrapped)

      default operator ?

    <?-
      Multi-param: constructor argument order matches type parameter order.
      (A, B) maps to (first, second) positionally.
    -?>
    Entry of type (A, B)
      first as A?
      second as B?

      Entry() as pure
        first :=? A()
        second :=? B()

      Entry() as pure
        ->
          a as A
          b as B
        first :=? a
        second :=? b

      getFirst()
        <- rtn as A: A()
        rtn :=? A(first)

      getSecond()
        <- rtn as B: B()
        rtn :=? B(second)

      default operator ?

  defines program

    ConstructorArgTypeDemo()
      stdout <- Stdout()

      // === SINGLE PARAM INFERENCE ===

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

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

      // === MULTI PARAM INFERENCE (positional) ===

      record <- Entry("id-001", 42)
      stdout.println(`Entry set: ${record?}`)

      first <- record.getFirst()
      if first?
        stdout.println(`First: ${first}`)

Common mistakes

E06050 — Constructor parameter types must match the type parameters in the same positional order. For 'Entry of type (A, B)', the first constructor parameter must be type A and the second must be type B. Reversing them breaks the positional correspondence the compiler uses for type inference. See ek9 -h E06050 for details.

Incorrect:

Entry of type (A, B)
      first as A?
      second as B?

      default Entry() as pure

      Entry() as pure
        ->
          a as B
          b as A

Correct:

    Entry of type (A, B)
      first as A?
      second as B?

      Entry() as pure
        first :=? A()
        second :=? B()

      Entry() as pure
        ->
Other ways to ask this
  • What is E06050 constructor argument must match parametric types?
  • Why must constructor parameter order match type parameter order?
  • How does EK9 validate generic constructor signatures?

Coming from another language?

Java: no constructor-to-type-param mapping (erasure). C++: template argument deduction from constructor (C++17 CTAD). Rust: no constructor syntax, uses 'new' pattern. Go: no constructor, zero-value + explicit init. Kotlin: constructor infers from usage like Java. EK9: strict positional mapping between type params and constructor params.

Keywords: order, match, inference, E06050, type-parameter, generic, type, constructor, constant, argument, positional