dart-lang / dart-lang/language
Disallow private members from being accessed without `this`
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
The main problem, illustrated by dart-lang/sdk#57710, is that this code results in a runtime error and not a compiler error
```dart
// a.dart
class A { int _a = 1; }
void test(A a) => print(a._a); // <-- relies on [A] having a private member [_a]
```
```dart
// b.dart
import "a.dart";
class B implements A { }
void main() => test(B()); // Compiles with runtime error
```
This error occurs because declaring `test` in the same library as `A` allows us to access `A._a`, but doesn't guarantee that all `A`s will have a member named `_a` -- for example, `B` does not. dart-lang/sdk#57710 forces `B` to provide its own (ie, override) `_a`, but I have a different solution: disallow this entirely. Another example:
```dart
// a.dart
class Value {
int _value = 1;
void _print() => print(_a); // (1)
int add(Value other) => this._value + other._value; // (2) and (3)
}
int addValues(Value a, Value b) => a._value + b._value; // (3)
```
```dart
// b.dart
import "a.dart";
class Value2 implements Value {
@override
int add(Value other) => -1; // some custom override not involving [_value]
}
void main() {
Value1().add(Value2()); // Error: Value2 does not have member `_value`
Value2().add(Value1()); // Ok
addValues(Value1(), Value2()); // Error: Value2 does not have member `_value`
}
```
In total, there are three few ways to access a private member:
1. In a public method, using `this`: is always safe since, if the class is implemented, the method will be reimplemented
2. In a private method, using `this`: is always safe since it is impossible to call the method if the class is implemented
3. From any other location, or when not using `this`: is unsafe since the object may be an `implements` subclass
It's a breaking change, but disallowing option 3 eliminates runtime errors in exchange for safer code. The root of the issue is that `test` is separated from `A` but depends on an intimate knowledge of `A` and should thus be a part of it. The above example can be rewritten as:
```dart
// a.dart
class A {
int _a = 1;
void test() => print(_a);
}
```
```dart
// b.dart
class B implements A {
/// we must implement [test] such that it doesn't depend on [A._a]
@override
void test() => print(1);
}
void main() => B().test();
```
Overall, if either dart-lang/sdk#57710 or this proposal is accepted, it will reduce the difference between implementing and extending (see dart-lang/sdk#57805), which can also simplify the [Class Modifiers proposal](https://github.com/dart-lang/language/blob/master/accepted/future-releases/class-modifiers/feature-specification.md).
I think this is a lot more breaking than dart-lang/sdk#57710 though, and so it's probably fine if this one is dropped in favor of that. In general, dart-lang/sdk#57710 favors a less breaking approach while this issue favors a design that more cleanly establishes what is and isn't private.
Contributor guide
Research direction
Start with the three private-member access cases and the examples in the issue, then read the linked Class Modifiers feature specification at accepted/future-releases/class-modifiers/feature-specification.md. Compare this proposal with dart-lang/sdk#57710 and #57805. Done means reaching and documenting a language-design decision; the issue names no implementation files or tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100