Catcall checkpoints

These examples should be considered for all catcall solutions:

Generic lists

The default semantics of the catcall solution should work for generics like the following:

local
  any_list: LIST [ANY]
  string_list: LIST [STRING]
  any_linked_list: LINKED_LIST [ANY]
do
    -- Should be allowed with default semantics
  any_list := any_linked_list
  any_list.put ("abc")
  any_linked_list.put (5)
 
    -- Has to be forbidden with default semantics
  any_list := string_list
end

Faces of covariance

The following example shows the difference between generic covariance and non-generic covariance.

class A [G]
feature
  f (t: T) do end
  put (g: G) do end
end
 
class B [G]
inherit A [G] redefine f end
feature
  f (u: U) do end
end
 
local
  a_any: A [ANY]
  a_string: A [STRING]
  b_any: B [ANY]
  b_string: B [STRING]
  t: T
do
  a_any := a_string
  a_any.put (5) -- catcall for generic feature
  a_any.f (t) -- no catcall
 
  a_any := b_any
  a_any.put (5) -- no catcall
  a_any.f (t) -- catcall for non-generic feature
 
  a_any := b_string
  a_any.put (5) -- catcall for generic feature
  a_any.f (t) -- catcall for non-generic feature
end

A safe and expressive solution should have a way to allow all valid cases and prohibit the invalid ones. This needs a way to address the generic and non-generic features indiviually.

Contravariance

class SORTER [G]
feature
  sort (a_list: LIST [G]; a_comparator: COMPARATOR [G])
    do
        -- Somewhere in the loop:
      a_comparator.compare (l_string_1, l_string_2)
    end
end
 
class EXAMPLE
feature
  make
    local
      l_list: LIST [STRING]
      l_string_sorter: SORTER [STRING]
      l_any_comparator: COMPARATOR [ANY]
    do
        -- Should be allowed to sort the string list with an
        -- ANY comparator
      l_string_sorter.sort (l_list, l_any_comparator)
    end
end

Solutions which pass this test are likely to be expressive for agents as well.

Agents

For agents, we will look at an example with the type T and a subtype U. We will see an agent declaration and then check which arguments that are allowed:

Calling agents

local
  an_agent: PROCEDURE [ANY, TUPLE [T]]
    -- An agent which takes an argument of type T.
do
    -- The following calls are currently valid and should remain valid.
  an_agent.call ([T])
  an_agent.call ([U])
 
  an_agent.call ([T, ...])
  an_agent.call ([U, ...])
end

These basic cases mostly rely on the tuple conformance rules to work properly.

Assigning agents

To allow anything that is safe is more tricky. It requires the ability to model contravariance.

local
  an_agent: PROCEDURE [ANY, TUPLE [T]]
    -- An agent which takes an argument of type T.
do
  agent_empty := agent () do end --> PROCEDURE [ANY, TUPLE []]
  agent_any := agent (a: ANY) do end --> PROCEDURE [ANY, TUPLE [ANY]]
  agent_t := agent (t: T) do end --> PROCEDURE [ANY, TUPLE [T]]
  agent_u := agent (u: U) do end --> PROCEDURE [ANY, TUPLE [U]]
  agent_tt := agent (t: T; t2: T) do end --> PROCEDURE [ANY, TUPLE [T, T]]
 
    -- This assignment is naturally allowed as they are the same type.
  an_agent := agent_t
 
    -- The following assignments are save but prohibited by the current implementation.
    -- If they were allowed it would benefit event driven programming in Eiffel a lot.
    -- As agents currently are modeled over the generic mechanism any solution which
    -- solves the "comparator problem" is likely to pass here too.
  an_agent := agent_empty
  an_agent := agent_any
 
    -- The following assignments are currently permitted. This is not the correct
    -- behavior  as you could either create a catcall by passing a T argument to the 
    -- `agent_u' or pass the wrong number of arguments by passing a [T] tuple to the 
    -- `agent_tt'.
  an_agent := agent_u
  an_agent := agent_tt
end