Ticket #192 (closed enhancement: fixed)

Opened 3 years ago

Last modified 3 years ago

Improve support for generic roles

Reported by: stephan Owned by: stephan
Priority: major Milestone: OTDT_1.3.0_M4
Component: language Version: 1.2.5
Keywords: Cc:

Description

It should be clarified in the OTJLD what can and what cannot be done with generic roles.

spawning from #190:

After generic base classes had to be ruled out (cf. OTJLD §2.1.2(e)) we are speaking about examples of this kind:

1  public team class  MyTeam {
2    protected class MyRole<T> playedBy MyBaseClass {
3      void rm() { ... }
4      void rm() <- after void bm(); // RAW TYPE MyRole!
5    }
6    protected class PlainRole playedBy OtherBaseClass {
7      void receiveRole(MyRole<String> r) { ... }
8      void recieveRole(MyRole<String> r) <- after void processBase(MyBaseClass b);
9    }
10   void teamMethod(MyBaseClass as MyRole<String> r, MyBaseClass b) {
11     MyRole<Date> otherRole = new MyRole<Date>(b);
12   }
13 }

Rules have to make sure, that every instantiation of MyRole substitutes the parameter T with a real type. Let's distinguish these cases

  1. Explicit creation using role constructor (line 11) - OK.
  2. Declared lifting (line 10) - OK.
    (cannot allow MyBaseClass as MyRole - is a raw type).
  3. Lifting of callin parameter, callout return (e.g., line 8) - OK
  4. Lifting call target in callin binding (line 4): NOT OK missing type argument

We could still allow raw types and just issue a warning, but since these uses occur in the very context where the generic role is defined, admitting raw types here seems to undermine the original intentions of generics.

Thus, it seems we cannot accept callin bindings in generic role classes. This is unfortunate, but unavoidable.

Change History

Changed 3 years ago by stephan

A notice regarding item 4. above has been added to OTJLD §4.1(b).

All cases involving liftig should also be reconsidered, because if lifting finds an existing role, it cannot be guaranteed that the role was created using the right type parameter.

Changed 3 years ago by stephan

  • status changed from new to accepted

One more thought:

public BaseClass<T> {
        T selectT(T one, T other) { return one; }
}

public team class TeamClass {
        protected RoleClass<T> playedBy BaseClass<T> {
                T selectT(T one, T other) <- replace T selectT(T one, T other);
                callin T selectT(T one, T other) { /* ... */ }
        }
}

In this situation the role class implementation can safely use T assuming it represents the same type as the base object's type parameter. It seems there are no restrictions for this pattern.

Note that the lifting constructor will of course have the following signature:

   ...
   protected class RoleClass<T> playedBy BaseClass<T> {
        public RoleClass<T>(BaseClass<T> aBase) { }
   }
   ...

ensuring that both instances have the same type parameter.

Changed 3 years ago by stephan

  • milestone changed from OTDT_1.2.6 to OTDT_1.3.0

It seems, we can support two cases

  • Unbound roles with type parameter (no further constraints(?))
  • Roles bound to a parameterized base class where both classes share the same type parameter(s). Here the team context has (normally) no information about parameter substitution, but the role-base communication is type-safe including lifting and lowering.

Still this needs more work both in specifying and implementing. Not for 1.2.6.

Changed 3 years ago by stephan

  • type changed from defect to enhancement

r21697 moves towards truely supporting the option from comment:2.

Hierarchy of DependentTypeBinding:

  • cleanup constructors of DependentTypeBinding and descendants
    • extract some initializations into methods so sub class has a chance to prepare before invoking this initialization
    • don't copy AreMethodsComplete to trigger creation of parameterized method bindings
    • fade out registerAnchor in favor of letting environment do all caching
    • TODO: avoid accessing environment via Config
    • TODO: avoid calling these constructors directly (vs. via environment)
  • don't let DependentTypeBinding and descendants override methods that can best be reused from ParameterizedTypeBinding
    • generic methods need to be instantiated
    • enclosing can safely be used from the field
  • create role type bindings only using the ifc-part (LookupEnvironment.createParameterizedType, see also RoleTypeBinding.<init>)
    TODO: this currently conflicts with ParameterizedTypeBinding.getRealClass
    TODO: more consistency on who is responsible for getRealType switching
  • don't unnecessarily wrap a weakened type binding.
  • share type variables between role class/ifc (RoleSplitter)
  • support instantiating generic baseclass via baseclass()
  • remove field DependentTypeBinding._type, but use ParameterizedTypeBinding.type

Implement lifting with generics:

  • declared lifting with generics:
    • when switching from ifc to class part respect existing type arguments (LiftingTypeReference and ParameterizedTypeBinding.getRealClass)
  • resolve potential lifts using erasures, type checking already happened at source level (param-pass through at role-level)
  • trickery regarding _OT$lift_dynamic methods:
    • consistently use raw types: since input is generalized to Object we cannot unify type parameters like in regular lift methods
      • don't wrap binary version to avoid inheritance conflict
    • new TagBits.HasWrappedSignature to consistently avoid double wrapping, needed so we can cheat by manually setting a return type

Other locations that need to consider generics, now:

  • support ParameterizedSingleTypeReference to be resolved via base import scope
  • declare role var within callin wrapper with type argument
  • TODO PlayedByAttribute needs to store/retrieve type arguments, too.

Pass: A.1.7-otjld-generic-baseclass-3

Fail: A.1.7-otjld-generic-baseclass-4

  • team compiles OK, but produces illegal generic signature (with empty '<>').

Changed 3 years ago by stephan

  • summary changed from Clarify restrictions for generic roles to Improve support for generic roles

Changing the title since we're way into better support for generics, rather than just specifying the limits.

r21757, r21758 bring some more changes in implementation strategies that are worth documenting:

  • typeVariables are no longer shared between class/ifc-parts (see comment:4) because that disturbed the parameter substition process -- from a generics point of view there's nothing special about the class/ifc splitting
  • a few more locations to create a parameterized type after other conversions:
    • TeamModel.strengthenRoleType,
    • RoleTypeBinding.superInterfaces (by letting the super-method do the job ;-)
    • LiftingTypeReference.resolveType

Another thing worth documenting:

  • role caches are declared using the raw types of role and base, because the role's type parameter(s) are not available in the scope of the cache field (team). This requires to suppress a few warnings re raw types in generated code.

Other recent commits relating to this issue:

Also in this context several commits cleaned up the management of weakened types:

Finally, this issue also triggered some improvements regarding role types anchored to a method parameter (the connection was in DependentTypeBinding.getUnpositionedClone which became obsolete):

Tests A.1.7-otjld-generic-baseclass-1 thru A.1.7-otjld-generic-baseclass-5 pass.

Changed 3 years ago by stephan

  • status changed from accepted to closed
  • resolution set to fixed

r21760 updates the OTJLD (including full update in otdt.ui.help) to reflect the changes in this ticket.

Changed 3 years ago by stephan

  • milestone changed from OTDT_1.3.0 to OTDT_1.3.0_M4
Note: See TracTickets for help on using tickets.