-
Notifications
You must be signed in to change notification settings - Fork 1.7k
NNBD_TOP_MERGE(FutureOr<int?>, FutureOr*<int*>) produces incorrect result . #40553
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
It appears that NNBD_TOP_MERGE works as expected for the given two types. I've added this case to our test cases for NNBD_TOP_MERGE: https://dart-review.googlesource.com/c/sdk/+/134823. It seems that the observed behavior here is due to the run-time semantics of |
I am not sure that the expectations are correct here. We call method getType() declared on the base class A. |
@crelier wrote:
It is indeed a tricky issue whether the 'implements @crelier, is it correct to say that the approach you are describing will use the type argument specified for the superclass? For example: // Opted in.
class A<X> { void foo() { print(X); }}
class B1 extends A<int?> {}
class B2 extends A<int> {}
// Opted out.
class C extends B1 implements B2 {}
// Opted in.
class D extends C {}
void main() {
D().foo(); // 'int?'.
} We will of course not achieve any notion of consistency, because the type system is unsound with respect to nullability until all code is nnbd. But it seems more consistent to me if After all, Note that I'm assuming that this is not prohibitively difficult to implement, or prohibitively expensive in terms of program size or speed. Wouldn't it be possible to create each instance of One thing that could make this difficult would be if methods in |
@eernstg wrote:
Yes, the implementation ignores implement clauses, except in type tests. Type arguments are solely defined by the chain of super types, and are not influenced by the interface types. Technically, yes, "int?" would be printed in your example, but only if it was a valid example:
We could 'erase' nullability in super types of classes declared in legacy libraries. In this case, it would happen for class C (and D). Your question about breaking a potential optimization is a good one. I do not think it is a problem, but I would have to double check. |
Cf. dart-lang/language#841 as well. @crelier wrote:
It should be a valid example. https://github.com/dart-lang/language/pull/789/files was introduced in order to use the 'consolidated' approach whereby each class has an interface which is computed based on its direct superinterfaces, rather than traversing the entire superinterface graph. During the discussions where we arrived at this model, we constantly used examples where a class indirectly implements conflicting superinterfaces (like I just discussed this with Johnni, and it is also his understanding of the consolidated model: Each class has a set of member signatures, and a set of implemented interfaces, and that's what we refer to when considering static subtype relationships and instance method invocations etc. He also agrees that the class The nnbd spec doesn't go into all details in this area, but it indicates that the legacy class should have a run-time representation where the type argument for the conflicting superinterface is the legacy type (end of this section):
In the mixin example here, I have created a class that has two applications of the same mixin with different type arguments. If the implementation expects to be able to look up the value of the type argument (say, if If there is a need to implement support in backends for having two distinct values for the type argument to |
Reopening this issue, as it was closed prematurely (mea culpa). I just revisited it and indeed, the example above is not rejected by CFE anymore. However, the example for which this issue was opened is still behaving as before, because the erasure proposed in dart-lang/language#841 does not apply for this example. Note that issue #40524 is related to this issue. |
Several things happened around dart-lang/language#841, and I'm proposing that we conclude that the type arguments passed along the superclass chain determine the run-time values of type variables. In particular, the mitigation based on Consider the example from here: // Opted in.
class A<X> { void foo() { print(X); }}
class B1 extends A<int?> {}
class B2 extends A<int> {}
// Opted out.
class C extends B1 implements B2 {}
// Opted in.
class D extends C {}
void main() {
D().foo(); // 'int?'.
} If we adopt the proposal I mentioned above then @crelier wrote:
If we adopt the proposal I mentioned then the CL that performs this erasure should not be landed. I sincerely hope it did not take a big effort to create it. There are several reasons why I ended up arguing that we should rely on the actual type arguments passed along the superclass chain (that is: I'm arguing that the question which is issue dart-lang/language#841 should be answered with a "no"). First, if any code is generated // With null-safety.
Type typeOf<X>() => X;
class A<X> {
bool get g => <X>[].runtimeType == typeOf<List<int>>();
}
class B1 extends A<int?> {
bool get test => super.g; // Inlined, optimized to `=> false`.
}
class B2 extends A<int> {}
// Legacy.
class C extends B1 implements B2 {}
// With null-safety.
void main() {
print(C.test);
} If we insist that This means that we must generate overriding versions of some methods (or do something else, such that we don't end up having optimizations like the one in By the way, we can't just use erasure along the superclass chain to obtain the appropriate result, because it's possible for the bottommost class (the role played by // With null-safety.
Type typeOf<X>() => X;
class A<X> {
bool get g => <X>[].runtimeType == typeOf<List<int>>();
}
// Legacy.
class B1 extends A<int> {
bool get test => super.g; // Inlined, optimized to `=> true`.
}
// With null-safety.
mixin M<X> on A<X> implements A<X> {
bool get g => super.g;
}
class C extends B1 with M<int?> {}
void main() {
print(C.test);
} In this situation, The situation is quite messy no matter which way we go, but I'm pretty sure the approach where we rely on the type arguments actually passed along the superclass chain is less messy than the version where we take the mitigated type argument value and force it into all superclasses. |
@eernstg writes:
No worries at all. I will abandon the CL and I am closing this issue, since the code behaves as expected and there is nothing to fix. |
Dart VM version: 2.8.0-edge.aadcb4418b1a7ccbb74a7cc925ad55020ce4a924 (Thu Feb 6 02:00:49 2020 +0000) on "linux_x64"
According to the NNBD Spec
NNBD_TOP_MERGE(FutureOr<int?>, FutureOr*<int*>)
should beFutureOr<int?>
.Please run the following source:
testlib_out.dart:
test.dart:
It produces the following output:
I.e.
NNBD_TOP_MERGE
result isFutureOr<int>
instead ofFutureOr<int?>
here.The text was updated successfully, but these errors were encountered: