-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disallow private members from being accessed without this
#2918
Comments
If A is declared like this and the test method is public, then the author of A should probably put the 'base' modifier on the class requiring extension and disallowing implementing. This situation could be an error, (when a public member of a library accesses a private member of a class that can be implemented). |
You're right that disallowing implementation will avoid this bug, but the point of this issue is to show how the current behavior is broken, with the hope that fixing it will bring But anyway, with the current behavior in mind,
The point is that if |
Ahh, you are right, this isn't a problem with methods, so its a bad interaction between library based privacy and class implementation. |
As @lrhn pointed out, in the following example, it is not enough to allow // a.dart
class Optimist {
final int _value;
Optimist(this.value);
// This private member is referenced inside its declaring class, but can still cause an error
Optimist operator+(Optimist other) => Optimist(_value, other._value);
}
// b.dart
class Pessimist implements Optimist { }
void main() => Optimist() + Pessimist(); // error So I added a new explanation to the top comment and I think we should eliminate option 3. Overall, the core of the problem is that in the following code, class A { }
// --------
class B extends A { }
class C implements A { } and while there are pros and cons to |
this
What you are defining here is non-interface methods. Rather than conflating it with privacy, consider it a separate kind of declarations (which may be distinguished using separate name syntax or by using modifiers at the declaration). An So, as a strawman, let's say such declarations are introduced using names starting with two underscores. Then you cannot write: class Optimist {
final int __value;
Optimist(int value) : __value = value;
Optimist operator+(Optimist other) => this.__value + other.__value; // INVALID
} because We can say that dynamic invocation, Using private naming isn't necessarily the best choice. base class StringBuilder {
protected final List<String> chunks = [];
void add(String string) {
chunks.add(string);
}
String toString() {
if (chunks.isEmpty) return "";
if (chunks.length == 1) return chunks.first;
var result = chunks.join();
chunks..clear()..add(result);
return result;
}
}
// Maybe in a different library
class StringWriter extends StringBuilder {
void addAll(Iterable<String> strings, [String separator = ""]) {
if (separator.isEmpty) {
super.chunks.addAll(strings); // Can access `chunks` on this/super.
} else {
super.chunks.addSeparated(strings, separator); // Some extension method.
}
}
}
void main() {
var writer = StringWriter();
if (writer.chunks.isEmpty) { // INVALID
// ...
}
} So, a different concept, more general than just avoiding unsafe private member access. (If this was possible, I'd probably start making all my private members protected too. But you don't have to, |
I fully agree that what I'm describing here is more in line with Rewriting the existing behavior to be more like |
Library privacy solves multiple problems. The primary problem is having "private" names that cannot conflict with names from other libraries. Going for They just don't work as well with |
In general, yes, it's nice if we can use static analysis to completely eliminate a class of runtime failures. At the same time, static analysis does have a cost:
Those mean that it's not necessarily to the right call to make something a compile time error. Null safety is a good example of this trade-off: Even before safety, users were generally able to write robust Dart code that didn't fail with null reference errors at runtime. When they moved to null safety, they were forced to start thinking about where to use With null safety, we believe that null reference errors are a large enough source of user pain that it's worth the cost. And we also believe that the annotations on which variables are nullable helps users better reason about the invariants in their code. With this issue, Dart has always had this bug where a library can receive an instance of one of its own types and try to access a private member on it. That access will fail if the object is actually an instance of a class outside of the library implementing the library's interface. But, in practice, I almost never hear about users running into bugs because of this. On the other hand, it's very useful and convenient to be able to access private members on instances other than this. Implementing equality is the classic example going all the way back to Smalltalk, but it comes up frequently. I think class modifiers will solve the problem for the (likely small number of) users that care about this particular invariant. But I don't think it would be a net benefit if we made a more sweeping change to the language to completely eliminate it. The runtime errors are just not a big source of pain, and the constraints you would have to fit within to eliminate them are. |
One requirement which has been noted for the // a.dart
abstract class A {
abstract int _a;
void test() => print(this._a); // Not safe.
} import 'a.dart';
class B extends A {}
void main() => B().test(); // Compiles, but throws at run time The example above is just the most blatant one. There are various ways to end up in a situation where a given private member like Of course, dart-lang/sdk#58179 will flag class |
The main problem, illustrated by dart-lang/sdk#57710, is that this code results in a runtime error and not a compiler error
This error occurs because declaring
test
in the same library asA
allows us to accessA._a
, but doesn't guarantee that allA
s will have a member named_a
-- for example,B
does not. dart-lang/sdk#57710 forcesB
to provide its own (ie, override)_a
, but I have a different solution: disallow this entirely. Another example:In total, there are three few ways to access a private member:
this
: is always safe since, if the class is implemented, the method will be reimplementedthis
: is always safe since it is impossible to call the method if the class is implementedthis
: is unsafe since the object may be animplements
subclassIt'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 fromA
but depends on an intimate knowledge ofA
and should thus be a part of it. The above example can be rewritten as: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.
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.
The text was updated successfully, but these errors were encountered: