Skip to content

Should eliminating implicit casts include casts during iteration. #264

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

Closed
leafpetersen opened this issue Mar 11, 2019 · 6 comments
Closed
Labels
nnbd NNBD related issues

Comments

@leafpetersen
Copy link
Member

Currently in Dart (or soon in the case of the spread), the following is a legal program:

main() {
  var a = <Object>[1, 2, 3];
  for(int x in a) {
    print(x);
  }
  <int>[...a];
}

There are implied casts on each element of the iteration, and on each element addition from the spread.

If we eliminate implicit casts, should we include these as well?

@leafpetersen
Copy link
Member Author

cc @rakudrama

@lrhn
Copy link
Member

lrhn commented Mar 12, 2019

You are spreading a list of objects into something where Object is not allowed, so it can clearly fail at run-time. If we disallow implicit downcast in other situations, then the reason for that is to make places where a run-time error can happen explicit in the code, rather than implicit. That reason should also apply here.

An issue is that we don't have a good syntax for casting the elements, so you are left to do <int>[...a.cast<int>()]. That's not particularly convenient.

(Maybe we could introduce declaration-site casts for parameters and iteration variables, like:
for (var x as int in a) ... or foo(Object x as int) { ... } which introduces a type check on any assignment, and promotes the variable to the as type. Like an explicit version of the cast we do for covariant parameters).

@eernstg
Copy link
Member

eernstg commented Mar 13, 2019

We could apply an idea here that we've discussed a few times in the past: Lazy checking of the components of a higher-order type (that is, a generic type or a function type). OK, we probably never made it explicit how to do this, but let's give it a syntactic form for concreteness:

main() {
  Iterable<lazy int> xs = <Object>[1, 2, "3"]; // OK, statically and dynamically.
  double d = xs[0]; // Compile-time error, elements considered `int`.
  int i = xs[0]; // OK statically. Dynamic check, succeeds.
  int j = xs[2]; // OK, statically, throws at run time.

  void Function(lazy num) f = (int) => print(x); // OK statically and dynamically.
  f(3.14); // OK statically, dynamic error.
}

This concept would then fit the treatment of the collection that you alluded to, that is, checks on each element during the iteration. Given that we can now write down the casts to get that semantics, we could eliminate the implicit casts and get the same typing situation explicitly as follows:

main() {
  var a = <Object>[1, 2, 3];
  for(int x in a as Iterable<lazy int>) {
    print(x);
  }
  <int>[...a as Iterable<lazy int>];
}

If we can find a syntax for "cast as needed" (that is: cast to context type, compile-time error if not assignable) that not everybody hates then we could also allow developers to use a shorter form (I'll use !! as a stand-in for that syntax):

main() {
  var a = <Object>[1, 2, 3];
  for(int x in a!!) {
    print(x);
  }
  <int>[...a!!];
}

The fact that !! means that we will use a "lazy type" is special for these syntactic positions, but they are well-defined (by the fact that their semantics includes iteration). So we'd simply say that the context type for the collection in an iteration with a variable of type T is Iterable<lazy T>, and similarly for a spread.

@munificent
Copy link
Member

I agree with the first two paragraphs of @lrhn's comment. Your suggestion is interesting, Erik, but feels like a lot of mechanism to me. I did spend some time a while back noodling on some ideas for pattern matching syntax and one thing I considered as a notation there for doing an explicit downcast. So maybe there is something to that.

I agree with Lasse that losing the implicit downcast here is particularly annoying because there isn't an easy notation to do it explicitly. If you don't want to use .cast<T>(), another option is:

main() {
  var a = <Object>[1, 2, 3];
  [for (var x in a) x as int];
}

@eernstg
Copy link
Member

eernstg commented Mar 20, 2019

@munificent wrote:

a lot of mechanism

Right, it does not fit well as a solution to this problem only, but this whole notion of "promise that this is a List<int> even though it's actual type argument is num, and then we will check that we get an int every single time we fetch an element from it" has been on the table several times, for different purposes, and I think this fits well into that discussion.

So if we want some kind of "lazy higher-order promotion" anyway then this is just an obvious use case.

@leafpetersen
Copy link
Member Author

This is disallowed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nnbd NNBD related issues
Projects
None yet
Development

No branches or pull requests

4 participants