Skip to content

Type equality inconsistency for generic functions. #37484

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

Open
lrhn opened this issue Jul 10, 2019 · 5 comments
Open

Type equality inconsistency for generic functions. #37484

lrhn opened this issue Jul 10, 2019 · 5 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends.

Comments

@lrhn
Copy link
Member

lrhn commented Jul 10, 2019

When we generate a Type object for a generic function, dart2js normalizes dynamic bounds to be the same as Object bounds, and the VM doesn't even consider structurally equal types to be equal Type objects.

Example:

Type type<T>() => T;
main() {
  var t0 = type<void Function<X extends Object>(X) Function()>();
  var t1 = type<void Function<X extends Object>(X) Function()>();
  var t2 = type<void Function<X extends dynamic>(X) Function()>();
  var t3 = (<D>() => type<void Function<X extends D>(X) Function()>())<dynamic>();
  print(t0 == t1);  // false in VM, true in dart2js, even though it is *exactly* the same type.
  print(t1 == t2);  // false in VM, true in dart2js
  print(t2 == t3);  // false in VM, true in dart2js
  print(t1 == t3);  // false in VM, true in dart2js
  print(t1); // () => <T1>(T1) => void
  print(t2); // () => <T1>(T1) => void
  print(t3); // () => <T1 extends dynamic>(T1) => void in VM, () => <T1>(T1) => void in dart2js.
}

The VM is definitely wrong about equality of the structurally same type.
As to considering dynamic and Object as the same bound, I guess that's reasonable, even though a little inconsistent (a bound of void is not equal).

@eernstg
Copy link
Member

eernstg commented Jul 10, 2019

Note the overlap with #32782 / #32783, which is associated with the test language_2/type_alias_equality. That test uses type aliases as well, but the bug is actually in the treatment of the underlying type, which includes generic function types and also has a case with differently named type variables.

@vsmenon
Copy link
Member

vsmenon commented Jul 10, 2019

@lrhn is this a language bug to clarify the spec?

@eernstg
Copy link
Member

eernstg commented Jul 11, 2019

The spec was updated in #34447 to say that Type.== must evaluate to true iff the arguments are subtypes of each other. So the spec already requires the equalities t0 == t1, t2 == t3.

For the bounds of type parameters of generic function types, the spec relies on renaming (generic function types are explicitly considered to be the same type if they only differ by choice of names), and this is not sufficient to establish a subtype relationship like Function<X extends Object>() <: Function<X extends dynamic>(). We might consider that to be an unintended distinction (it seems inconsistent to me), and we could then change the subtype rules to say that also bounds must just be mutual subtypes.

This would also be a non-breaking change, according to the following test (which has no errors with the analyzer, also with --no-implicit-casts, and also not with dart or dart2js):

main() {
  void Function<X extends Object>(X) Function() f1 = null;
  void Function<X extends Object>(X) Function() f2;
  void Function<X extends dynamic>(X) Function() f3;
  void Function<X extends D>(X) Function() f4pre<D>() => null;
  var f4 = f4pre<dynamic>();
  f2 = f1; f1 = f2; f3 = f2; f2 = f3; f3 = f4; f4 = f3;
  f3 = f1; f1 = f3; f4 = f2; f2 = f4; f4 = f1; f1 = f4;

  void Function<X extends List<dynamic>>(X) f6;
  void Function<X extends List<FutureOr<void>>>(X) f7;
  f6 = f7; f7 = f6;
}

So I believe we can extract a spec change issue here: Change the subtype rules to use mutual subtyping for generic function type bounds—if we want to do that. If we don't want to change the subtype rules then dart2js is wrong on t1 == t2 and t1 == t3.

Other than that, there is still the issue with the VM.

@lrhn
Copy link
Member Author

lrhn commented Jul 11, 2019

Even if we change the specifiction to be "Type objects are == equal if they are the the same type" (which might be a good idea since that's almost what everybody implmenets), then "the same type" for structural types like function types still means that void Function<T>(T) Function() is the same type every time you write it. There is only one function type with that structure.

So, this is a VM bug.
Hardly a high priority one, though.

@vsmenon vsmenon added the area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. label Jul 11, 2019
@eernstg
Copy link
Member

eernstg commented Jul 12, 2019

PS: Unless we change the subtype rules, it is also a dart2js bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
Projects
None yet
Development

No branches or pull requests

3 participants