mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:39:48 +00:00
004f564f40
A quirk of analyzer and CFE implementations is that they resolve property gets such as `foo.bar` to specific field or getter declarations that may be not be directly defined on the target type; for example if `foo` has type `B`, and `B` extends `A`, and `A` contains a field called `bar`, then `foo.bar` is considered to refer to `A.bar`, for example: class A { int? bar; } class B extends A {} f(B foo) { print(foo.bar); // Resolved to `A.bar`. } This is in contrast with the language specification, which makes a clean distinction between class _declarations_ and the _interfaces_ implied by those declarations. While a class declaration can contain (among other things) both getters and fields, which might be concrete or abstract, an interface doesn't distinguish between getters and fields, and is inherently abstract. The advantage of the analyzer/CFE approach is that it allows more intuitive error messages and "go to definition" behavior, which benefits users. But it has some ill-defined corner cases involving complex class hierarchies, because not every property access can be resolved to a unique declaration (sometimes a getter is multiply inherited from multiple interfaces, for example). The language spec approach has the advantage of being well-defined and consistent even in situations involving complex class hierarchies. When I initially implemented field promotion, I took advantage of this quirk of the analyzer and CFE implementations, so that I could make property gets that refer to field declarations promotable, while keeping property gets that refer to abstract getter declarations non-promotable. This caused unpredictable behaviors in the ill-defined corner cases. It also meant that in certain rare cases, a property access might not be promoted even when it would be sound to do so (e.g. a property access might refer to a private abstract getter declaration, but the only concrete _implementation_ of that abstract getter was a final field). This CL changes the rule for promotability so that any get of a private property is considered promotable, provided that the containing library doesn't contain any concrete getters, non-final fields, or external fields with the same name. It no longer matters whether the private property refers to a field or a getter. This rule is simpler than the old rule, restores the spec's clean distinction between class declarations and interfaces, and allows more promotions without sacrificing soundness. For additional details please see the breaking change announcement at https://github.com/dart-lang/sdk/issues/54056, as well as the original change proposal at https://github.com/dart-lang/language/issues/3328. Fixes https://github.com/dart-lang/sdk/issues/54056. Fixes https://github.com/dart-lang/language/issues/3328. Change-Id: I38ffcb9ecce8bccb93b1b2586a1222a0fb1005a7 Bug: https://github.com/dart-lang/sdk/issues/54056 Bug: https://github.com/dart-lang/language/issues/3328 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/337609 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
140 lines
3.9 KiB
Dart
140 lines
3.9 KiB
Dart
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// This test contains a test case for each condition that can lead to the front
|
|
// end's `NullableExpressionCallError`, for which we wish to report "why not
|
|
// promoted" context information.
|
|
|
|
class C1 {
|
|
C2? bad;
|
|
// ^^^
|
|
// [context 1] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 7] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
class C2 {
|
|
void call() {}
|
|
}
|
|
|
|
instance_method_invocation(C1 c) {
|
|
if (c.bad == null) return;
|
|
c.bad();
|
|
//^^^^^
|
|
// [analyzer 1] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// ^
|
|
// [cfe 7] Can't use an expression of type 'C2?' as a function because it's potentially null.
|
|
}
|
|
|
|
class C3 {
|
|
C4? ok;
|
|
C5? bad;
|
|
// ^^^
|
|
// [context 2] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 8] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
class C4 {}
|
|
|
|
class C5 {}
|
|
|
|
extension on C4? {
|
|
void call() {}
|
|
}
|
|
|
|
extension on C5 {
|
|
void call() {}
|
|
}
|
|
|
|
extension_invocation_method(C3 c) {
|
|
if (c.ok == null) return;
|
|
c.ok();
|
|
if (c.bad == null) return;
|
|
c.bad();
|
|
//^^^^^
|
|
// [analyzer 2] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// ^
|
|
// [cfe 8] Can't use an expression of type 'C5?' as a function because it's potentially null.
|
|
}
|
|
|
|
class C6 {
|
|
C7? bad;
|
|
// ^^^
|
|
// [context 3] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 9] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
class C7 {
|
|
void Function() get call => () {};
|
|
}
|
|
|
|
instance_getter_invocation(C6 c) {
|
|
if (c.bad == null) return;
|
|
c.bad();
|
|
//^^^^^
|
|
// [analyzer] COMPILE_TIME_ERROR.INVOCATION_OF_NON_FUNCTION_EXPRESSION
|
|
// [analyzer 3] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// ^
|
|
// [cfe 9] Can't use an expression of type 'C7?' as a function because it's potentially null.
|
|
}
|
|
|
|
class C8 {
|
|
C10? bad;
|
|
// ^^^
|
|
// [context 4] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 10] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
class C10 {}
|
|
|
|
extension on C10 {
|
|
void Function() get call => () {};
|
|
}
|
|
|
|
extension_invocation_getter(C8 c) {
|
|
if (c.bad == null) return;
|
|
c.bad();
|
|
//^^^^^
|
|
// [analyzer 4] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// ^
|
|
// [cfe 10] Can't use an expression of type 'C10?' as a function because it's potentially null.
|
|
}
|
|
|
|
class C11 {
|
|
void Function()? bad;
|
|
// ^^^
|
|
// [context 5] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 11] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
function_invocation(C11 c) {
|
|
if (c.bad == null) return;
|
|
c.bad();
|
|
//^^^^^
|
|
// [analyzer 5] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// ^
|
|
// [cfe 11] Can't use an expression of type 'void Function()?' as a function because it's potentially null.
|
|
}
|
|
|
|
class C12 {
|
|
C13? bad;
|
|
// ^^^
|
|
// [context 6] 'bad' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
|
|
// [context 12] 'bad' refers to a public property so it couldn't be promoted.
|
|
}
|
|
|
|
class C13 {
|
|
void Function() foo;
|
|
C13(this.foo);
|
|
}
|
|
|
|
instance_field_invocation(C12 c) {
|
|
if (c.bad == null) return;
|
|
// Note: the CFE error message is misleading here. See
|
|
// https://github.com/dart-lang/sdk/issues/45552
|
|
c.bad.foo();
|
|
// ^^^
|
|
// [analyzer 6] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
|
|
// [cfe 12] Can't use an expression of type 'C13?' as a function because it's potentially null.
|
|
}
|