Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose isSubclassOf #58261

Open
Solido opened this issue Nov 19, 2024 · 5 comments
Open

Expose isSubclassOf #58261

Solido opened this issue Nov 19, 2024 · 5 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@Solido
Copy link

Solido commented Nov 19, 2024

Since the new addition of sealed, base and other OOP keywords,
dart offers more ways to model real world systems.

When using inheritance it is common pattern in chain of command as an exemple
to ask if a class is a subclass of specific parent to switch implementation.

Kotlin expose isSubclassOf, java Class.isAssignableFrom, etc

I'm now facing a case where I need to handle a structure in parallel with the
classes declarations to handle hierarchy and it's getting large and error
prone to manually manage each new entry per class.

On Flutter Forum user tenhobie suggested this:

bool typeIsSubtypeOf<T1, T2>(T1 _, T2 __) => <T1>[] is List<T2>;

that works on simpler cases only.

When moving away from flutter and trying to model business relying on OP
this would be a considerable addition for large code base simplification.

Thank you for your feedbacks.

@FMorschel
Copy link
Contributor

Similar to dart-lang/language#3837 and my answer on StackOverflow:

Just to add up to lrn's answer.
You could also do something like:

extension NullableObjectsExtensions<T> on T {
  bool isSubtypeOf<S>() => <T>[] is List<S>;
  bool isSupertypeOf<S>() => <S>[] is List<T>;
}

So this way you can test any variable anywhere.

@Solido
Copy link
Author

Solido commented Nov 19, 2024

They are similar but your request would require much more upwork to be delivered so I went with just a single method as Munificent expressed the dart does not have much request of those features.

Those previous codes works only on simplistic class extensions but got mixed up pretty quickly in real cases. That's why I ended up with my own code to manage the class types :/

Munificent also shared that the infos is present in the VM but it's mainly a question of priorities.

@lrhn
Copy link
Member

lrhn commented Nov 19, 2024

that works on simpler cases only.

It works for types, but not really for values.

It does work for all types. If you need to check two types for whether one is a subtype of the other (and presumably at least one is a type variable, otherwise the answer is constant), then doing:

bool isSubtype<T1, T2>() => <T1>[] is List<T2>;

will work for any two types. The only thing one can regret is that it requires allocating an object.

You can use a simple class than List, like:

class _SubtypeHelper<T> {}
bool isSubtype<T1, T2>() => _SubtypeHelper<T1>() is _SubtypeHelper<T2>;

If you have two objects, then asking whether the runtime type of one is a subtype of the runtime type of the other is not possible.
It's also not necessarily desirable since it breaks abstraction.
You can always ask whether an object has a type that you know. You can't ask if it has a type that you don't know about.
That allows objects to have secret, private implementation types that cannot affect other code, because they other code cannot ask about them.
However, if you can use the runtime type of an object "as a type", then you can break abstraction and see that, fx, isSubtypeOf(1, 2) but not isSubtypeOf(1, -0x8000000000000000). Why, because 1 and 2 are both instances of Smi, but -0x8000000000000000 is an instance of a different integer implementation type.
Using objects as types leaks their runtime type. Not quite reflection, but too close for comfort.
Using just their runtimeType and comparisons on type objects may allow over to bypass that, because runtimeType can be overridden to lie about the type. (But there are ways around that, do it can still leak.)

The referenced Java and Kotlin operatons work on Class objects, which would correspond to the Dart Type objects.
That again suggests reflection. A corresponding Dart operation could be:

class Type {
  external bool operator <=(Type other);
  external bool operator >=(Type other);
}

which would allow something like T <= Object or T >= Null.
(Or similar extension methods, if we don't want to break subtypes of Type.)

That does mean that a type object needs to retain enough information to do subtype checking, which is something AoT compilers can otherwise tree-shake for any type that isn't statically detectable as occurring in a subtype check. Any use of type1 <= type2 can cause the entire type hierarchy of every type mentioned and the runtime type of every object created, to be retained in the compiled program. Today a Type object doesn't retain any type relations.

It's probably clear that I'm fairly strongly against adding reflection-like operations. Java can get away with it, and having full reflection, because it's JIT compiled. It has the source class files available, so anything it hasn't compiled into the JIT'ed program already is still there.
Dart is fairly unique in being an interface-based AoT-compiled language. It disallows reflection when AoT-compiled because there is no good way to make it work wellwithout including the entire source program, which is a no-go for AoT compilation, especially for the web.
(I'll also admit that I haven't grokked the use case here. There are two implementations of the same API, and someone has to figure out which version a value is from, in order to choose a matching value for it? Could each object just have an API-key that they expose, which says where they come from?)

I'd sugges using dart:mirrors, but mainly because I know it won't work anywhere in practice. 😈

@Solido
Copy link
Author

Solido commented Nov 19, 2024

Thank you for the very instructive information.

I'm working on a large project with deep and wide implementations and used an Enum inside parent classes for implementation. It's verbose but work as intended as it's a simple field comparaison. It has large unit testing covering.

When replacing this implementation with the specified method they fails with case everything going positive... Should this method able to handle mixins too and rich compositions?

I'm gonna try to find the culprit but for sure they're not behaving the same, not what I would expecting from isSubTypeOf. Enum implementation does like a field inside the Vm would simply, I assume, keep a marker of the parent class.

The use keep is very standard OOP. Replace a default set of implementations with another compatible based on values. But in a collection of subclasses I need to compare with which one is the parent for a transparent swap.

You can't ask if it has a type that you don't know about.
You gave a lot of informations and it hints me with a sense of how the Dart VM handle infos, or forget them, but I'll need to spend more time to have a clear vision of what's possible.

Using objects as types leaks their runtime type. Not quite reflection, but too close for comfort.
I get that and why I've too deal so often with frustrations in my code because of that but the benefits and security overweight that.

Yet with my respect to the dart team priorities I still feel I'm gonna deeeeply miss this feature ;)

Thanks everyone for helping me out.

@dart-github-bot
Copy link
Collaborator

Summary: User requests isSubclassOf function for easier runtime type checking in Dart, similar to Kotlin and Java, to simplify inheritance-based code. Current workaround is insufficient.

@dart-github-bot dart-github-bot added area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug labels Nov 20, 2024
@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core and removed area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. labels Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

4 participants