Coder Social home page Coder Social logo

Comments (8)

lrhn avatar lrhn commented on June 17, 2024

My initial thought is that it is too dangerous. Not because I can think of a concrete problem, but because I cannot convince myself that I can imagine all the possibilities.

If we do allow inline classes to implement class interfaces, but not to implement two different instantiations of the same interface, then an online class which implements List<int>, T may or may not be valid depending on the type bound to T. That seems potentially problematic.
An implements List<int>, Iterable<T> would be easier to reject eagerly, because T might not be int.

from language.

eernstg avatar eernstg commented on June 17, 2024

Sure, this is new. However, they managed to handle this kind of feature in C++ for many years, so it shouldn't be intractable.

from language.

lrhn avatar lrhn commented on June 17, 2024

If we assume it works, then inline class V<X> implements X {...} is definitely covariant in X.

We know that the representation type is a subtype of X. It may not be precisely X, we could have inline class W<X, Y extends X> implements X { final Y _; }, but then it'll be another type variable or Never, because that's the only types that are subtypes of type variables. Which means it's very likely always going to be the representation type itself, so what we have is a completely general wrapper which can add/overlay members on any type.

That probably does have some use.
Take:

inline class Logable<T> implements T {
  final T _it;
  Logable(this._it);
  void log() {
    Logger.current.logObject<T>(_it);
  }
}

I'm not seeing much use-case that isn't already handled by an extension <T> on T, but it's probably also not any more dangerous than that. Just a different way to do the same thing.

(I think we require all type parameters to an inline class to occur covariantly in the representation type. We may also want them to occur covariantly in implements clauses. But inline class V<X> implements void Function<X> {...} isn't new to this, so it's not an argument in either direction.)

from language.

eernstg avatar eernstg commented on June 17, 2024

a completely general wrapper .. on any type

Right, that was exactly the kind of situation I had in mind.

I'm not seeing much use-case that isn't already handled by an extension <T> on T,

The most obvious difference is that the members added by this kind of inline class are sticky, so we can have a library where some return type is a Logable<int>, and it will remain loggable as long as we're using type inference to remember that it has type Logable<int> rather than just int, and we don't have to import any extra libraries just because we need to know about an extension.

I think we require all type parameters to an inline class to occur covariantly in the representation type.

We do. The point is that if V2 <: V1, but V2 is erased to void Function(int) at run time and V1 is erased to void Function(num) then we'd need to generate dynamic type checks at up casts involving type variables (or we'd violate soundness).

inline class V<X> {
  final void Function(X) it;
  V(this.it);
  List<void Function(X)> unsound() {
    print('it is void Function(X): ${it is void Function(X)}');
    return [it];
  }
}

Y doCast<X extends Y, Y>(X x) => x; // Check `x is Y` at run time?

void main() {
  V<num> v = doCast(V<int>((int i) {}));
  var list = v.unsound();
  print(list.runtimeType); // List<void Function(num)>.
  print(list.first.runtimeType); // void Function(int).
}

This program compiles and runs because this variance error hasn't yet been implemented. It prints it is void Function(X): false, which shows that we can reach a situation where void Function(X) can be denoted, but it does not have that type, and there is no dynamic type error.

Then we proceed to create a list of type List<void Function(num)> which contains an element of type void Function(int). This is a soundness violation.

Similarly for the requirement that type variables can only occur covariantly in superinterfaces: We don't want to have dynamic checks on up casts.

But I agree that there is nothing new here in that respect: implements X is a covariant occurrence of X in a superinterface.

from language.

lrhn avatar lrhn commented on June 17, 2024

I'll admit that I just tried to write, essentially, inline class FancyNum<T extends num> implements T { ... }.

It's nicer than just implements num because you can keep the int or double type that you already have.

The same should apply to inline class FancyIterable<T, L extends Iterable<T>> implements L { ... } that can add extra members to iterables, but also be applied to lists and sets without losing list/set-ness. (More useful if inference gets smarter, or we get inline class FancyIterable<L extends Iterable<final E>> implements L { ... }.)

from language.

eernstg avatar eernstg commented on June 17, 2024

For every parameterized type of the form V<T> where inline class V<X> implements X {...}, it is known that every member in the interface of T is accessible on a receiver of type V<T>.

I think this should make it possible for every member invocation e.m() to determine statically whether or not it is an invocation of this kind, and then transform it to (e as T).m() and proceed with that. Backends would get a note that this cast need not occur at run time.

If T is again an inline type then the process may be repeated. It is a separate check on the inline class declarations that this process will terminate.

from language.

munificent avatar munificent commented on June 17, 2024

However, they managed to handle this kind of feature in C++ for many years, so it shouldn't be intractable.

Is it relevant that C++ only type-checks after template instantiation? C++'s type system essentially has no notion of generics at all.

from language.

eernstg avatar eernstg commented on June 17, 2024

Is it relevant that C++ only type-checks after template instantiation?

I think we can assess the burden imposed on the static analysis directly for the case where we allow implements X where X is a type variable declared by the enclosing inline class.

inline class I<X extends B>(T rep) implements S1, .. Sk, X {
  ...
}

In the body of I we can invoke members of B on this; it does not change the interface of this in the body of I that I is invoked with a specific set of actual type arguments. So we should not need to re-run the static analysis on the body of I for each actual type argument which is passed to I (and we'd never do that, of course, so it's lucky that we won't need to do it ;-).

For each subtype judgment of the form I<T> <: S we will need to check whether T <: S as well as all the other things we'd check in order to determine C<T> <: S or not for some generic class C. That does not seem to be intractable.

For each member access myI.m() where the receiver has static type I<T>, we would use the existing rules about inline classes to resolve invocations of members declared in inline classes (I and its superinterfaces), and we would also include members declared by the interface of T.

The new thing here is that we must declare a compile-time error if some name n is a member of both the interface of T and the interface of I.

So we're definitely touching some delicate stuff here, but it seems likely to me that it will be tractable.

from language.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.