Skip to content

Conversation

eernstg
Copy link
Member

@eernstg eernstg commented Feb 7, 2020

I raised an issue in #793 about the nnbd mechanism for computing a member signature named m where multiple most-specific member signatures named m occur in the direct superinterfaces, and the class itself does not declare a member named m. Here is an example showing the main cases (with the member signature of m in comments):

abstract class A1 { Object? m(Object? x); }
abstract class A2 { dynamic m(dynamic x); }
abstract class A3 { void m(void x); }

abstract class A12 implements A1, A2 {
  // With current nnbd rules, and with this PR: Object? m(Object?).
} 

abstract class A13 implements A1, A3 {
  // Current: void m(void).
  // With this PR: Object? m(Object?).
}

abstract class A23 implements A2, A3 {
  // Current: void m(void).
  // With this PR: dynamic m(dynamic).
}

abstract class A123 implements A1, A2, A3 {
  // Current: void m(void).
  // With this PR: Object? m(Object?).
}

The problem that this PR aims to resolve is that it is a breaking change, and not a useful one as far as I can see, to get so many situations where return types and parameter types are void. The same issue arises at any level of nesting, so we'd have a similar example where the type ends up being List<void> with the current nnbd rules, and List<Object?> with this PR.

The breakage occurs in two ways:

  • When a return type is void rather than Object? or dynamic then an error is likely to occur whenever the returned value is used (another possible outcome is that some local variable now gets inferred type void, which may cause a similar problem when that variable is used).
  • When a parameter type is void rather than Object? or dynamic in a method declaration where the superinterfaces are used to infer the parameter type ('override inference'), the same problems arise as those with the return type, except that it affects all usages of said parameter.

There is no guarantee that the pre-nnbd rules would choose any particular top type, because those rules used the textual order of the superinterfaces. But if that rule gave rise to the choice of a return or parameter type which was not void, then it will be a breaking change if just one of the most specific member signatures (from any superinterface) has void. So in that sense it is a breaking change, even though there can be cases where there is no breakage.

This PR ensures that void will only occur in a member signature named m when all the direct superinterfaces that have a member signature named m agree that this position in the signature should be the type void. If just one of them says dynamic then dynamic prevails, and if just one of them says Object? then it prevails over both void and dynamic.

This is meaningful relative to the return type. Let's say we can choose that m returns void (that is, we use the current nnbd rules) or Object? (with this PR). In this case at least one superinterface promises that m returns a usable object, and then the current class should also return a usable object, hence the type should be Object?. When m is called and the static type is a superinterface where m returns void, the caller assumes that the object should be discarded. But that's also allowed when the return type is Object?, so that client can use the invocation correctly as well.

@eernstg eernstg changed the title Adjusted NNBD_TOP_MERGE such that void does not prevail Adjust NNBD_TOP_MERGE such that void does not prevail Feb 7, 2020
@eernstg
Copy link
Member Author

eernstg commented Apr 2, 2020

Closing, the change is made in #904.

@eernstg eernstg closed this Apr 2, 2020
@eernstg eernstg deleted the adjust_nnbd_top_merge_feb20 branch April 2, 2020 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants