-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Can't initialize constants from other constants #29098
Comments
This doesn't work because The reason we don't make class Foo {
final Bar _bar;
const Foo(Bar bar) : _bar = bar;
Bar get bar { log("Bar accessed"); return _bar; }
} then it would break the client code. This change would now be a breaking change, even if it doesn't change the API of the class at all. We want that rewrite to be valid and non-breaking, so we can't just make final field access a compile-time constant expression. So, what if we allowed you to mark fields as constant? class Foo{
const Bar bar;
const Foo(this.bar);
} It'd just be a final field, but with a marker that says that it can be used in a constant expression. At that point, we could make access to The disadvantage is the cognitive overhead. Whenever you make a const class, you must now consider, for every field, whether you want to make it constant. The safe default is to not make it const, because you can always change it to const in the future, or you can change it to a getter in the future. Making every final field const will lock you into the current design. Also, the next request will likely be "const getters" which can only contain potentially constant expressions, just to be able to get out of the corner that some authors will have painted themselves into. It's all definitely doable, but it's not clear that it's worth its own complexity. So far we have chosen not to do it. |
For what it's worth, this causes problems in Flutter at the moment. We want as many of our widgets to be instantiated
Everything is a breaking change. Adding a field is a breaking change (someone might have Changing a field to a getter is a breaking change even without allowing const access through a getter, because it changes the behaviour of the member. I don't really buy this as an argument. Field/getter symmetry is already not a thing in practice. |
There are breaking changes and breaking changes. Not everything is a breaking change, or even potentially a breaking change. Refactoring is exactly about doing semantics preserving restructuring. It's based on some changes being safe and non-breaking. There are things that clients of your code can reasonably depend on, and things that are not reasonable (unless you go out of your way to promise them anyway). You can always change the behavior of your class, and then you are responsible for the breakage. if your class is intended for subclassing, or for use as a mixin, there are things you can no longer change without breaking clients that are using your class as intended. If you break someone who depends on the whitespace of your code, they are responsible. I'm not saying it's not annoying, but you couldn't reasonably be expected to predict that. Changing a field to a getter or vice versa, while keeping the visible behavior, is not a breaking change. That is a quite deliberate choice in the Dart language. It means that you can evolve your class without locking yourself into a specific implementation. When you can't do that, you get something like the Java "always have a private field and public get/set methods" coding style. We are trying to avoid that. We definitely do not want to make all final fields of const classes locked into being fields. |
I'm not convinced that there's a difference between a breaking change that breaks something documented as unstable and a breaking change that breaks something implied to be stable due to some language policy. In either case, your client is broken. In either case, you risk losing them. |
You do have a point. That is why it is so important to know which changes are not breaking changes in any way. Switching between a getter and field (with the same visible semantics, obviously) is always a non-breaking change. That's a good thing. |
It's not, actually. Take this code: class A {
final int _x = 1;
int get x => _x;
}
class B extends A {
@override
int get x => super.x + 1;
}
void main() {
print(new B().x); // prints 2
} ...which compiles cleanly, executes cleanly, and has no analyzer warnings. Now remove the getter and replace it with a field. The analyzer fails with a STRONG_MODE_INVALID_FIELD_OVERRIDE error. class A {
final int x = 1;
}
class B extends A {
@override
int get x => super.x + 1; // STRONG_MODE_INVALID_FIELD_OVERRIDE
}
void main() {
print(new B().x);
} |
Yes, that is a problem with strong mode (#28117). It'll be fixed. |
Another way this could be solved, rather than the const field issue, is by allowing compile-time-evaluatable functions to be considered const. So for example if a getter just returned a field, it could be const. That would solve many other problems too, for example it would allow const asserts to do much more elaborate tests, and would allow const Widgets to be created from lists via List.generate, etc. |
I'm sure we can extend I don't think It's a trade-off, though. Making more expression const, just because we can, isn't a goal in itself. Keeping it simple, understandable and explainable is also a goal. |
The text was updated successfully, but these errors were encountered: