google / reflectable.dart

Reflectable is a Dart library that allows programmers to eliminate certain usages of dynamic reflection by specialization of reflective code to an equivalent implementation using only static techniques. The use of dynamic reflection is constrained in order to ensure that the specialized code can be generated and will have a reasonable size.
https://pub.dev/packages/reflectable
BSD 3-Clause "New" or "Revised" License
374 stars 56 forks source link

isSubtype exception for classes without parents #320

Closed kaomoneus closed 1 year ago

kaomoneus commented 1 year ago

I observed following counter-intuitive behaviour, consider following code:

import 'package:reflectable/reflectable.dart';

import 'main.reflectable.dart';

class MyReflection extends Reflectable {
  const MyReflection() : super(
    typeCapability,
    typeRelationsCapability,
  );
}

const reflector = MyReflection();

@reflector
class Base0 {}

@reflector
class Base1 {}

void main() {
  initializeReflectable();
  var b0 = reflector.reflectType(Base0);
  var b1 = reflector.reflectType(Base1);

  if (b1.isSubtypeOf(b0) /* crash: NoSuchCapabilityError */) {
    print("All good!");
  }
}

The reason is that xxx.reflectable.dart sets superClassIndex = noCapabilityIndex even for classes with capability but without parents. It looks like problem lays in builder_implementation.dart, _classMirrorCode. I believe that extra case to superclassIndex initialization should solve this issue.

eernstg commented 1 year ago

The documentation comment on isSubtypeOf actually says the following:

  /// Note that this method can only be invoked successfully if all the
  /// supertypes of the receiver are covered by the reflector, and a
  /// `TypeRelationsCapability` has been requested.

For a class/mixin that does not declare any superinterfaces explicitly (that is, we do not have an extends clause, a with clause, an implements clause, or, for a mixin, an on clause), we always have Object as an implicitly provided superinterface. (The class Object itself is an exception, but we don't have to worry about how that is possible, the compiler/analyzer can always give platform provided libraries a non-standard treatment if needed.)

In particular, both Base0 and Base1 have Object as a supertype.

If you add superclassQuantifyCapability in the constructor named MyReflection then you do not get the run-time error.

In some situations it might be inconvenient to cover the required types because the resulting amount of generated code is so large (because you have to cover all supertypes). If that is true for your project then you could consider using types directly for the purpose of determining subtype relationships:

bool isSubtypeOf<X, Y>() => <X>[] is List<Y>;

void main() {
  print(isSubtypeOf<int, num>()); // 'true'.
  print(isSubtypeOf<String, num>()); // 'false'.
  print(isSubtypeOf<List<List<num>>, Iterable<Object>>()); // 'true'.
}

You can create connections to mirrors of types by comparing the actual type (like X above) to the reflectedType of the TypeMirror.

I'll close this issue because this is all working as intended (but we can add further comments here if needed).

kaomoneus commented 1 year ago

Ok, thank you for explanation!