E07835: 'collect as T' needs T to have a no-argument constructor that can be called where the pipeline is written

Category: E07xxx Full resolution · Reported in phase: FULL_RESOLUTION · Classification: COLLECT_REQUIRES_DEFAULT_CONSTRUCTOR · All error codes

What went wrong

'| collect as T' constructs a NEW T with its no-argument constructor - exactly as if 'T()' were written where the pipeline is - and then pipes each item into it through its '|' operator. So T must have a no-argument constructor, and that constructor must be one that can be called from where the pipeline is written. One of the two is not true here: the message names T and says which.

  • "T has no no-argument constructor": a type that declares only constructors WITH parameters still gets a generated no-argument one, so the usual cause is a sub-type of a class whose no-argument constructor is private - no no-argument constructor is generated for the sub-type, because its implicit 'super()' could not reach the private one.
  • "T's no-argument constructor is private (or protected)": it exists, but 'T()' could not be written here either. A 'default private' no-argument constructor is how a type whose own constructors set a property is allowed to leave it unset (E07175), so making a T with it from outside the type would observe that unset property.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 954 (How do I use collect as to accumulate stream items into a...)

Pattern

The 'collect as' pattern works in three steps: create an instance with no arguments ('T()'), pipe each item of the pipeline into it ('collector | item', which calls 'operator |'), and return the accumulated instance as the result. The type needs BOTH a no-argument constructor callable where the pipeline is and an 'operator |' that accepts the pipeline's element type.

How to fix it

  1. Give T a public no-argument constructor that sets every property (or initialise every property where it is declared), or collect into a type made to accumulate and build the T from its result
  2. Inside T itself a private no-argument constructor IS callable, but one that leaves a property unset is still rejected (E08264)
  3. Check that 'operator |' is also defined (see E07830)

The [EXAMPLE] section below shows a Counter whose super's no-argument constructor is private (so Counter has none), and a Counter record that 'collect as' can make.

Common causes

  • A sub-type of a class whose no-argument constructor is 'default private'
  • A type whose own no-argument constructor is 'default private'
  • Collecting into a type that was designed to be built only through a factory

See also

  • E07835: collect-as needs a callable no-argument constructor (this error)
  • E07830: unable to find pipe operator for type
  • E07175: the default constructor must be private when it leaves properties unset
  • E08264: a constructor that leaves properties unset
  • E07840: missing iterator method

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E07835: 'collect as T' needs T to have a no-argument constructor that can be called where the pipeline is written

#!ek9
defines module error.e07835.bad

  defines class

    Tally as open
      count as Integer?

      default private Tally()

      Tally()
        -> start as Integer
        this.count: start

      protected increment()
        count++

      default operator ?

    //No no-argument constructor: Tally's is private, so none is generated here
    Counter extends Tally
      Counter()
        -> start as Integer
        super(start)

      operator |
        -> item as Integer
        if item?
          increment()

  defines function

    countAll()
      <- rtn as Counter?

      //'collect as' makes the Counter with no arguments
      rtn: for i in 1 ... 3 | collect as Counter

Fixed: Give the target type a public no-argument constructor

#!ek9
defines module error.e07835.good

  defines record

    Counter
      count as Integer: Integer()

      default operator ?

      operator $ as pure
        <- rtn as String: String()
        if count?
          rtn := $count

      operator |
        -> item as Integer
        if item?
          if not count?
            count: 1
          else
            count++

  defines program

    CountAll()
      stdout <- Stdout()
      result <- for i in 1 ... 3 | collect as Counter
      stdout.println($result)

Questions and answers

These Q&A entries show E07835 and its fix: