E06132: a collection cannot carry a type invariant: its only usable constraints are whole-container predicates ('contains'), which are 'require' preconditions, and a mutating operator would re-validate on every '+=' - so a mutation could break it; constrain the ELEMENT type instead ('List of MyConstrainedElement'), which holds for every element however the collection is assembled

Category: E06xxx Type hierarchy checks · Reported in phase: TYPE_HIERARCHY_CHECKS · Classification: CONSTRAINED_TYPE_BASE_CANNOT_BE_UNSET · All error codes

What went wrong

A collection cannot carry a type invariant.

The only constraints usable on a container are whole-container predicates such as 'contains' - and those are 'require' preconditions, not type constraints. A mutating operator would have to re-validate the invariant on every '+=', so any mutation could silently break it.

How to fix it

Constrain the ELEMENT type instead, then build the collection from it (the [EXAMPLE] section below: a 'Tag' String constrained to match /^[a-z]+$/, and a List of Tag).

The invariant then holds for every element, however the collection is assembled.

Rationale

A constraint is a promise about a value that holds for the whole life of that value. A container's contents change after construction, so a container-level promise cannot be kept without re-checking on every mutation. Constraining the element makes the promise hold by construction.

Common causes

  • Trying to constrain 'List of X' rather than X
  • Expecting a size or emptiness constraint on a collection type
  • Confusing 'constrain as' (a type invariant) with 'require' (a precondition)

See also

  • E06131: constrained base is not copyable
  • E06120: type cannot be constrained (genus not permitted)

Examples

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

Fails with E06132: a collection cannot carry a type invariant: its only usable constraints are whole-container predicates ('contains'), which are 'require' preconditions, and a mutating operator would re-validate on every '+=' - so a mutation could break it; constrain the ELEMENT type instead ('List of MyConstrainedElement'), which holds for every element however the collection is assembled

#!ek9
defines module error.e06132.bad

  defines type

    //The RIGHT tool: constrain the ELEMENT, then hold a collection of it. The guarantee holds for
    //every element however the list is assembled.
    Tag as String constrain as
      matches /^[a-z]+$/

    //The wrong tool: a constrained collection. 'List of String' can never be unset, so the
    //constrained type has no unset state to borrow.
    Tags as List of String constrain as
      contains "req"

    //Same for any other parameterised generic.
    Lookup as Dict of (String, Integer) constrain as
      contains "req"

Fixed: Constrain the ELEMENT type, then hold a collection of it

#!ek9
defines module error.e06132.good

  defines type

    Tag as String constrain as
      matches /^[a-z]+$/

  defines function

    tags()
      <- rtn as List of Tag: [Tag("req"), Tag("opt")]