-
Notifications
You must be signed in to change notification settings - Fork 0
Metaclass type bound example seems wrong #2
Description
The example of using metaclasses in a typed way seems broken (or else there's something that I'm missing, and that hence could perhaps use better explanation ;) ).
class B<T extends A.class> {
T GetBySmth() {
return T.new(); // could even be new T() if we went that far
}
}The constraint is perfectly reasonable, as I understand this. It says that T will be instantiated with a type which is a subtype of A.class: that is, that things of type T will be objects that implement the interface A.class. But T.new() doesn't return a subtype of A.class as I understand it (at least not for non-trivial A - hopefully this example isn't relying on the property that A is trivial?). So the typing of GetBySmth is wrong - it claims to return an instance of a meta class, but actually returns an instance of a class. Compare:
class A {
static int s() => 3;
int d() => 4;
}
// B is parameterized over class types subtyping A.class
// B has a method foo which given a Type object
// implementing the interface of A.class, returns it.
// B also has a method bar which will instantiate T, producing an instance
// of some subtype of A.
class B<T extends A.class> extends A {
B(int x);
T foo(T c) => c;
A bar() => T.new(); // known safe, since T extends A.class
}
// We can instantiate B as follows:
B b = new B<A.class>(0);
// Calling foo on a B returns the Type object for A, which has an s method which
// returns an integer, but does not have a d method.
b.foo(A).s(); // ok
b.foo(A).d(); // bad
// Calling bar on a B returns a new instance of A, which has a d method,
// but not an s method
b.bar().s(); // bad
b.bar().d(); // ok
// Even though B extends A, we cannot instantiate B with B.class, since B.class is not
// a subtype of A.class and hence does not satisfy the bound.
B badB = new B<B.class>(0); // bad
// If we ignore the type error, we can call bar, which tries
// to call the default constructor on B (which has none).
badB.bar(); // bad
// C is parameterized over instance types subtyping A
// C has a method foo which given a Type object implementing the interface of
// T.class, returns it.
// C also has a method bar which will attempt to instantiate T and return an instance
// of a subtype of A. If T has a default constructor,
// then the instantiation will succeed, but if not this will be a dynamic error.
class C<T extends A> extends A {
C(int x);
T.class foo(T.class c) => c;
A bar() => T.new(); // unsafe, may fail at runtime
}
// We instantiate C as follows:
C c = new C<A>(0);
// Calling foo on a C returns the Type object for A, which has an s method which
// returns an integer, but does not have a d method.
c.foo(A).s(); // ok
c.foo(A).d(); // bad
// Calling bar on a C returns a new instance of A, which has a d method,
// but not an s method
c.bar().s(); // bad
c.bar().d(); // ok
// We can instantiate C with anything that extends A
C okC = new C<C>(0); // ok
// But calling bar still blows up, since the call to new is dynamic and C has
// no default constructor.
okC.bar(); // badI think the issue with the method bar above is what the original example was trying to get at: given a type parameter T with a class type bound (as in B above), we know the contract associated with the type object for T (specifically in this case, whether or not it has a default constructor), but we don't have any way to get from a class type to the instance type, and we also cannot usefully instantiate T with any subtypes.
Alternatively, given a type parameter T with an instance type bound (as in C above), we know the contract associated with instance of T, and we can instantiate T with useful subtypes of the bound. We can get the type object for T (simply by using it as a type literal), and we can get the class type for T (via T.class) but we do not know anything about the contract implied by T.class, and hence we can only do T.new() (and other static methods) in a type unsafe fashion.
This is presumably the point of the comment at the bottom of the examples section: in order to get the best of both worlds, you need both some way of specifying the additional class constraint on the type parameter, and some way of allowing more than one type to satisfy the constraint.