dart-sdk/tests/language/inference_update_2/why_not_promoted_disabled_error_test.dart
Paul Berry 004f564f40 Allow "field promotion" to apply to abstract getters.
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>
2023-12-07 23:40:27 +00:00

123 lines
7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2023, 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.
// Tests that the appropriate "why not promoted" context messages are shown when
// field promotion is disabled.
//
// The context message advising the user that field promotion is only available
// in Dart 3.2 and above is only shown if upgrading to Dart 3.2 would make field
// promotion work; otherwise, the context message explains the reason why the
// field would not be promotable even in Dart 3.2. Rationale: if a user,
// encouraged by a context message, decided to upgrade the required language
// version of their package to 3.2, only to discover that the field in question
// still wasnt promotable, that could be very frustrating.
// @dart=3.1
class C1 {
final int? _wouldBePromotable = 0;
// ^^^^^^^^^^^^^^^^^^
// [context 1] '_wouldBePromotable' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 8] '_wouldBePromotable' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
int? get _notField => 0;
// ^^^^^^^^^
// [context 2] '_notField' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 2] '_notField' refers to a getter so it couldn't be promoted. See http://dart.dev/go/non-promo-non-field
// [context 9] '_notField' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
// [context 9] '_notField' refers to a getter so it couldn't be promoted.
final int? notPrivate = 0;
// ^^^^^^^^^^
// [context 3] 'notPrivate' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 3] 'notPrivate' refers to a public property so it couldn't be promoted. See http://dart.dev/go/non-promo-public-field
// [context 10] 'notPrivate' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
// [context 10] 'notPrivate' refers to a public property so it couldn't be promoted.
int? _notFinal = 0;
// ^^^^^^^^^
// [context 4] '_notFinal' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 4] '_notFinal' refers to a non-final field so it couldn't be promoted. See http://dart.dev/go/non-promo-non-final-field
// [context 11] '_notFinal' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
// [context 11] '_notFinal' refers to a non-final field so it couldn't be promoted.
final int? _conflictingGetter = 0;
// ^^^^^^^^^^^^^^^^^^
// [context 5] '_conflictingGetter' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 12] '_conflictingGetter' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
final int? _conflictingField = 0;
// ^^^^^^^^^^^^^^^^^
// [context 6] '_conflictingField' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 13] '_conflictingField' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
final int? _conflictingNsmForwarder = 0;
// ^^^^^^^^^^^^^^^^^^^^^^^^
// [context 7] '_conflictingNsmForwarder' couldn't be promoted because field promotion is only available in Dart 3.2 and above. See http://dart.dev/go/non-promo-field-promotion-unavailable
// [context 14] '_conflictingNsmForwarder' couldn't be promoted because field promotion is only available in Dart 3.2 and above.
}
class C2 {
int? get _conflictingGetter => 0;
// ^^^^^^^^^^^^^^^^^^
// [context 5] '_conflictingGetter' couldn't be promoted because there is a conflicting getter in class 'C2'. See http://dart.dev/go/non-promo-conflicting-getter
// [context 12] '_conflictingGetter' couldn't be promoted because there is a conflicting getter in class 'C2'.
int? _conflictingField = 0;
// ^^^^^^^^^^^^^^^^^
// [context 6] '_conflictingField' couldn't be promoted because there is a conflicting non-promotable field in class 'C2'. See http://dart.dev/go/non-promo-conflicting-non-promotable-field
// [context 13] '_conflictingField' couldn't be promoted because there is a conflicting non-promotable field in class 'C2'.
}
class C3 {
final int? _conflictingNsmForwarder = 0;
}
class C4 implements C3 {
// ^^
// [context 7] '_conflictingNsmForwarder' couldn't be promoted because there is a conflicting noSuchMethod forwarder in class 'C4'. See http://dart.dev/go/non-promo-conflicting-noSuchMethod-forwarder
// [context 14] '_conflictingNsmForwarder' couldn't be promoted because there is a conflicting noSuchMethod forwarder in class 'C4'.
noSuchMethod(invocation) => 0;
}
test(C1 c) {
if (c._wouldBePromotable != null) {
c._wouldBePromotable.isEven;
// ^^^^^^
// [analyzer 1] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 8] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c._notField != null) {
c._notField.isEven;
// ^^^^^^
// [analyzer 2] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 9] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c.notPrivate != null) {
c.notPrivate.isEven;
// ^^^^^^
// [analyzer 3] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 10] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c._notFinal != null) {
c._notFinal.isEven;
// ^^^^^^
// [analyzer 4] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 11] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c._conflictingGetter != null) {
c._conflictingGetter.isEven;
// ^^^^^^
// [analyzer 5] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 12] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c._conflictingField != null) {
c._conflictingField.isEven;
// ^^^^^^
// [analyzer 6] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 13] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
if (c._conflictingNsmForwarder != null) {
c._conflictingNsmForwarder.isEven;
// ^^^^^^
// [analyzer 7] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 14] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
}
}
main() {}