-
Notifications
You must be signed in to change notification settings - Fork 198
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
Impact of adding private members #3826
Comments
That's what we do if there is no implementation. It shouldn't make any difference whether there is an insufficient implementation or no implementation of a member that the |
(My 'Option 1' includes giving |
Why should Dart allow a private member without implementation in an abstract class at all? It can nowhere be implemented. |
It can be implemented on other classes inside the same library/file. |
@escamoteur wrote:
Agreeing with @FMorschel, here's a bit of motivation: We may want to use an abstract declaration of a private member abstract class A { int get _m; } // Better than `=> -1;`, where -1 is never a valid result.
class B1 extends A { get _m => 1; }
class B2 extends A { get _m => 2; }
void foo(A a) => print(a._m); However, this will allow a subclass of "OK, then Right, but we need to remember that, and we need to keep track of all subtypes that do not have an implementation of one or more private members (consider "OK, but then we just rename First, we still need to keep an eye on classes like Next, it could be inconvenient because we might well want All in all, I don't think it's a safe bet that we can just make it an error for a class which is capable of having a subtype in a different library to have one or more private members in their interface that do not have an implementation. I think there will be cases where that kind of situation is exactly what you want and need, and hence we might want to protect ourselves against those unimplemented private members in some other way. |
don't you think that if Dart gets a real |
That doesn't sound particularly likely... Library scoped privacy is aligned with the fundamental compilation granularity of Dart. (OK, cyclic imports can create small sets of libraries that always have to be compiled together, and some Dart compilers do whole-program optimization, but otherwise it's not unreasonable to say that we compile one library at a time).
This means that, as far as I can see, it isn't possible to use I tend to think that library privacy makes sense from one more point of view: We're generally working on libraries, and whenever you can change anything in a given library, you can also change everything in that library. (You may break clients, unless it's guaranteed that they don't rely on the things that you're changing, but you have the physical ability to make the changes). This means that private properties of a library are "your own ..private.. property" when you are working on a library. That would not be true at all if there could be clients anywhere out there who are depending on your |
But you have the same problem with normal members that get overridden by deriving classes outside of a library unless its a sealed class. An additional To me expressing privacy based on the location of a class, meaning in which files they are makes moving classes to different files difficult and it is more a privacy by convention but by clear statements in the class declaration. |
Well, doh. Then "Option 1" it is! |
Defining whether a class is accessible is not completely trivial. Probably not impossible, but it's one of those cases where if you forget to dot one i or cross one t, then you have a soundness problem, which may be escalated to a security issue depending on how deep in the compiler the soundness assumption is relied on. |
Exactly, that was my point. I just mentioned that So my proposal for the best way to play is that we lint every class that has a noSuchMethod thrower. Such a lint is guaranteed to cover all cases, and it is not hard to implement (the compiler obviously knows exactly when it is generating a noSuchMethod thrower). There may be other ways to detect the issue locally. E.g., dart-lang/linter#4975 aims to flag every private member invocation which is potentially going to invoke a noSuchMethod thrower. However, again, that's tricky. In the meantime, let's return to the topic of this issue: The CFE reports a compile-time error about Should the CFE stop reporting that error (and, of course, generate a noSuchMethod thrower instead)? This basically means that the error would go away, and the lint would (optionally) replace it. |
@escamoteur wrote, about different kinds of access control (
I'm not 100% sure what this problem is the same as. ;-) Anyway, if we only have That's the reason why I don't think
That would be a completely different kind of compilation stability, or at least I can't see how it would ever be possible to avoid recompiling. Let's say that library L contains a declaration that has a member
I agree on that one! It does constrain the complexity of client code that they can't access this particular member in any way. Every usage of said member must be in the code of the class that declares this member, or in the class of a subclass thereof. However, it's still possible for the value of a member which is private to dynamic d;
class A {
final this.list = <int>[];
A() { d = list; } // OK, we can access `list` here.
}
void main() {
d.add(24);
}
Sure, but the point is that as long as class If you move But then again, just don't move that class! ;-) If you insist on getting access to that class from some other library then let that other library export the class from L.
Privacy in Dart is one of the strictly maintained properties of the language. You can't break the privacy rules. |
This principle, while a nice thing to aim for as a guideline, is not in any way shape or form universally true in Dart, and I think never has been (even, I think, in Dart 1). Counter-example: //lib.dart
class A {
int _x = 3;
}
mixin M {
// String _x() => "hello";
} //main.dart
import "lib.dart";
class C extends A with M {}
void main() {
} This program is fine, but if you uncomment the line in I think my preference is to do nothing here (that is, ask the analyzer to start reporting this error). Is there any good reason we allow classes to exist with concrete methods which do not satisfy their interface? That does seem like the other obvious path here: just make |
Indeed. We should land this one, finally: #1626. Note that it includes cases that would work if we hadn't decided that we will report an error for mixin applications where "unrelated" concrete members are brought together (for example, they might have exactly the same signature and everything would just work, except that it is an error when they are private). This is clearly an example of a situation where the addition of a private member with an existing name will introduce an error in a different library. This particular error arises at a mixin application, and nowhere else. Perhaps we can just maintain that it is safe to add a member with a fresh private name: If it is not a compile-time error locally in that library L then it is also not a compile-time error in a library that imports L.
Well, that's what I've been saying for years, "just report those errors!". In this case it's an incorrect override. In other cases we've mentioned, the given class does not have an implementation of a private member. The effect is the same: If we try to execute that member at run time it will have to throw. One is not better or worse than the other. But I keep getting pushback based on arguments like "we cannot introduce compile-time errors in importing libraries because we have changed private declarations in this library". I'd say "Yes, we can!" 😁 |
Consider the following program consisting of two libraries:
This program is accepted by the analyzer as well as the common front end, and it throws at run time because the given instance of
B
does not have an implementation of_private
.I have argued (e.g., here) that this situation should be subject to a warning (or an error). However, the counterargument has been raised (e.g., here) that it is more important to avoid that the addition of a private declaration causes code in other libraries to fail.
Surely,
B
should not be an error with version 1. According to the "addition of private members is non-breaking" principle,B
should not be an error with version 2, either. Surely, then, version 3 cannot makeB
an error.So we accept that
B
does not have an implementation of_private
because it would be worse to report an error forB
based on such updates of the library that declaresA
.However, if we maintain that the addition of a private member should not cause an error to occur in a different library then we should also accept the following program with no compile-time errors:
The analyzer again accepts both versions of the code, but the common front end reports an error for version 2, where we have added the private declaration
A._private
:Again, we'd incur a run-time failure because the instance of
C
does not have an implementation of_private
(and the one which is declared inA
can not be used because it does not have the required signature). We aren't able to run it, though, because of the error.We should at least address the discrepancy such that all tools will agree on the existence/non-existence of a compile-time error.
@dart-lang/language-team, WDYT?
The text was updated successfully, but these errors were encountered: