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

Instantiate-to-bounds process produces different results depending on the nesting level of type variable for function return value. #33606

Closed
iarkh opened this issue Jun 25, 2018 · 6 comments
Assignees
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language).

Comments

@iarkh
Copy link
Contributor

iarkh commented Jun 25, 2018

  • Dart SDK Version: 2.0.0-dev.64.1
  • OS: Windows 10

The following source code describes parametrized function type via typedef (return value should have parameter type) and that try to assign some function to variable of the given type:

typedef F<X> = X Function();
F<F> testme() { return testme(); }

main() {
  F f1 = testme;
  F<dynamic> f2 = testme;
  F<F> f3 = testme;
  F<F<dynamic>> f4 = testme;
  F<F<F>> f5 = testme;
  F<F<F<dynamic>>> f7 = testme;
}

This passes for me, both dart and dart analyzer don't produce any errors or warnings here.

However, if I increase the nesting level when declare variables, both dart and analyzer starts to fail:

typedef F<X> = X Function();
F<F> testme() { return testme(); }

main() {
  F<F<F<F>>> f6 = testme;
  F<F<F<F<dynamic>>>> f7 = testme;
  F<F<F<F<F>>>> f8 = testme;
  F<F<F<F<F<dynamic>>>>> f9 = testme;
}

Looking into Instantiate-to-Bounds Spec, it seems like at least all cases like F<F<F<F...<F<dynamic>..>>>> should pass.

Sample output is:
$>dartanalyzer ttt.dart
Analyzing ttt.dart...
error - The function 'testme' has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → dynamic'. This means its parameter or return type does not match what is expected at ttt.dart:5:19 - strong_mode_invalid_cast_function
error - The function 'testme' has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → dynamic'. This means its parameter or return type does not match what is expected at ttt.dart:6:28 - strong_mode_invalid_cast_function
error - The function 'testme' has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → () → dynamic'. This means its parameter or return type does not match what is expected at ttt.dart:7:22 - strong_mode_invalid_cast_function
error - The function 'testme' has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → () → dynamic'. This means its parameter or return type does not match what is expected at ttt.dart:8:31 - strong_mode_invalid_cast_function
hint - The value of the local variable 'f6' isn't used at ttt.dart:5:14 - unused_local_variable
hint - The value of the local variable 'f7' isn't used at ttt.dart:6:23 - unused_local_variable
hint - The value of the local variable 'f8' isn't used at ttt.dart:7:17 - unused_local_variable
hint - The value of the local variable 'f9' isn't used at ttt.dart:8:26 - unused_local_variable
4 errors and 4 hints found.

$>dart ttt.dart
ttt.dart:5:19: Error: The top level function has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → dynamic'.
Change the type of the function or the context in which it is used.
F<F<F>> f6 = testme;
^
ttt.dart:6:28: Error: The top level function has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → dynamic'.
Change the type of the function or the context in which it is used.
F<F<F<F>>> f7 = testme;
^
ttt.dart:7:22: Error: The top level function has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → () → dynamic'.
Change the type of the function or the context in which it is used.
F<F<F<F>>> f8 = testme;
^
ttt.dart:8:31: Error: The top level function has type '() → () → () → dynamic' that isn't of expected type '() → () → () → () → () → dynamic'.
Change the type of the function or the context in which it is used.
F<F<F<F<F>>>> f9 = testme;
^
$>dart --version
Dart VM version: 2.0.0-dev.64.1 (Thu Jun 21 08:47:55 2018 +0200) on "windows_x64"
$>

@lrhn
Copy link
Member

lrhn commented Jun 25, 2018

The instantiate-to-bounds gives you an actual type of testme of

dynamic Function() Function() Function()

aka F<F<F<dynamic>>>. You get two of the function levels from the return type, and then testme is itself a function taking no arguments.

The assignments of f1 through f6 are valid assignments for something with that type.
The assignments f7 and up are not, because dynamic is not a subtype of F<dynamic>.

The assignment of dynamic Function() Function() Function() to a variable of type dynamic Function() Function() Function() Function() is unsafe because if you call it three times, you get dynamic and you expect dynamic Function(), and the dynamic might not be a function at all.

I'm guessing the strong-mode type system is extra strict here because it knows the exact type of testme, so it doesn't allow the implicit down-cast that is known to fail at runtime.

All-in-all, I think this works correctly.

@lrhn lrhn added the area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). label Jun 25, 2018
@iarkh
Copy link
Contributor Author

iarkh commented Jun 26, 2018

If so, should not instantiate-to-bounds check stops on the second level?
I mean, if type of testme is exactly F<F> - should F<F<dynamic>> be a correct type and <F<F<F<dynamic>>>> - NOT correct?

@lrhn
Copy link
Member

lrhn commented Jun 26, 2018

The return type of testme is exactly F<F<dynamic>> (aka dynamic Function() Function()).
The type of testme itself is then F<F<dynamic>> Function(), or F<F<F>>.

You are assigning testme to the variables, not testme(), so f5 and f7 pass as well.

@eernstg
Copy link
Member

eernstg commented Jul 4, 2018

Agreeing completely with @lrhn, I believe the topic here is exact types, that is #33307, and the rest is just a matter of subtyping relationships for which I believe we have no ambiguity or novelty here. Adding a reference to this issue in #33307.

So given that it works as intended (relying on exact types to be resolved elsewhere), I guess we could close this issue; or do you, @lrhn or @iarkh, see anything here beyond that?

@eernstg
Copy link
Member

eernstg commented Jul 4, 2018

Oh, one thing!: In tests, we should take care to avoid disturbances introduced by the not-so-well-defined approach to exact types. That is, when we have T f = testme we should also have something like T f = testme as T1 or T f = identity<T1>(testme); where T1 is the declared type of testme and we have X identity<X>(X x) => x; (whatever we choose must be known to guarantee that the exact type is eliminated, as is probably enough and identity should certainly be enough). We will then have a test where the pure subtyping relationship is handled (which may be significant also for tests testing other topics).

Cases that involve exact types would then exist in addition to the 'pure' test cases, or maybe the "exact cases" should be commented out until we have a clear approach to exact types, but we should at least cover the case where no exact types are involved as the primary ones. Note that exact types play a role for downcasts (i.e., the downcast variant of assignability), but they do not matter for upcasts, so there is no need to write extra code to eliminate the exact type unless there is a downcast.

@eernstg
Copy link
Member

eernstg commented Aug 27, 2018

I'd like to resolve this issue.

Checking again, we get exactly the errors that we would expect based on a traditional subtype requirement (that is, the 4 deeply nested cases in the second example of the initial text of the issue are flagged as errors): The downcasts are flagged and the upcasts (including the "same-type-casts" that we have with f5 and f7 in the first main) are accepted.

This means that there is nothing in this issue which is concerned with instantiate-to-bound or with function types that shows any unexpected behavior, and hence we should just close this issue.

The area where we do need clarification is in the use of exact types (we only get an error in all four cases of the second main because testme is given an exact type, otherwise the error cases would just have been a regular downcast, and there should not have been any errors).

However, I do not think that we should use this issue to clarify exact types, we already have #33307 for that.

Under the assumption that it is OK for dart and for dartanalyzer to use an exact type for testme and hence to reject downcasts at compile-time, the behavior reported for those two tools is correct.

So I'll close this issue now; this issue is still mentioned in #33307 as yet another example of a situation where exact types play a role, but we already have other references to such issues which are closed and which contain yet another example, so that's not a problem.

@eernstg eernstg closed this as completed Aug 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language).
Projects
None yet
Development

No branches or pull requests

3 participants