-
Notifications
You must be signed in to change notification settings - Fork 213
const
Constructor parameters are never considered const
#2000
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
Comments
A duplicate of #823, in some way. |
Note that it is slightly misleading to say that
You can have a class with a constant constructor that implements const canConst = CanConst(1);
const CanNotConst canNotConst = CanNotConst2(1);
const canNotConstContainer = CanNotConstContainer(CanNotConst2(1));
class CanConst {
final int value;
const CanConst(this.value);
}
class CanNotConst {
final int value;
CanNotConst(this.value);
}
class CanNotConstContainer {
final CanNotConst cnc;
const CanNotConstContainer(this.cnc); // No Error
}
class CanNotConst2 implements CanNotConst {
final int value;
const CanNotConst2(this.value);
} This is the part which is a duplicate of #823: class MyClass {
// ...
const MyClass.sameOther(int value)
// why does this work ...
: this.value = value,
// ... but this does not
other = MyOtherClass(value);
// ----------^^^^^^^^^^^^^^^^^^^^^
// Invalid const value
} @ltOgt, perhaps you could extract the non-duplicate parts of this issue into a new issue? |
The issue here is that We currently have no way to write It would break one assumption that Dart has so far been able to have: That every constant is created by a specific position in the program, and any constant expression creates precisely one constant value. That's what ensures that there cannot be more constants in a Dart program than linearly in the program size. (That's also why we can definitively disallow cycles in const constructors, because we are absolutely certain that coming back to the same constructor will hit the same So, all in all, it's not currently possible. It's a valid improvement to have that feature, but it's not without risk. |
You are right, I did not think about that.
Yes, this is what my issue boils down to, @lrhn did a better job bringing it to the point than I could. Maybe plus the note that the current
Both of these things feel natural to assume at first. void main() {
const a = A("b");
const b = a.v;
}
class A {
final String v;
const A(this.v);
} It might be nice to have a little more documentation on const constructors e.g. at https://dart.dev/guides/language/language-tour#constant-constructors |
This is a whole separate issue. The assumption in your code is that because
|
Can't the analyzer figure out both 1 and 2 ? |
Dart doesn't have a difference between a field and a getter. A field is simply sugar for a getter + setter combo, and a final field is sugar for just the getter. So I don't think so. |
Yes sorry, this was actually #1868. |
Well that case is more complex than this one. You had an interesting question in this case. Since The answer to your other case is a bit simpler, and you already said why: while the static type of |
@cedvdb wrote:
'2' is the property that the statically known declaration of A closed-world analysis can iterate over all class declarations and check this property, but that's not an acceptable constraint on a programming language: If we're writing a reusable library L then the subclass with this override may be written in a library L2 that we've never had any knowledge about, and there could then suddenly be a compile-time error in L because of code in L2. This means that libraries can't be reusable. |
Sorry I deleted my comment as I felt like the question fit more in the if variable issue but to give context to your answer it was: Wouldn't a better solution be to make the analyzer differenciate between a getter which returns the same value on sequential calls and when it might not be the case to allow for field not null promotion in |
The important distinction is not with/without side effects, it is whether we have or do not have a guarantee that the getter will return the same value each time. #1518 is aimed at that exact goal. |
@Levi-Lesches wrote:
This is an interesting question. A compiler could rely on properties of the actual value of a constant expression (in particular, we can know that I guess the most important reason why I wouldn't like to go down that path is that a large software system derives a lot of software engineering value from complexity reduction mechanisms, and this kind of detailed implementation-dependent reasoning is a violation of encapsulation, and it breaks the complexity reduction mechanisms. If we develop and maintain the software system relying on properties that are associated with interfaces (such as the types of expressions, and the members known to exist for such types, and the types of those members) then, ideally, we don't have to worry about the underlying implementation. However, types are a very rough approximation of a specification of the semantics of the software, so we will have to rely on a lot of informal information about the behavior of the software, in addition to the type/interface based reasoning. Dart does not have non-overridable method implementations (like This means that Dart's meta-contract with us as developers is that (1) we are allowed to create an implementation of any given interface afresh ( However, (1) is applicable to software evolution as well: When a given class is modified over time, it is known that clients cannot depend on the encapsulated properties, and this means that we are free to modify those properties (e.g., changing a stored property to a computed property, or vice versa). If we allow constant expression correctness to rely on knowledge about some of these encapsulated properties then they can't be modified any more. Crucially, we can't know whether there is a client who is depending on any particular property, because it might be in someone else's code, in a package that imports our code. So you'll have to be very careful about making a constructor My conclusion is that we shouldn't break the encapsulation, because that's expensive at the level of software engineering, even though it might look benign in the context of a small snippet of code. However, it's a completely different matter if we want to achieve a similar result by means of an interface property: If we have something like the stable/final getters of #1518, then we can declare that a particular getter is guaranteed to return the same object if it is invoked twice on the same receiver. Then we have a different contract with subtypes (they must maintain that property as well), but they are still free to override the implementation as needed. So my answer is: Yes, we can know that a getter of a constant object is constant, but we need to do it right. |
Absolutely (as Erik says, with a closed world assumption, it can know everything about the program structure). If the compiler does figure out that it's a final field of a const class, and allows you to access it in a const expressiion, then it means that the author of the class can no longer change their instance field to a getter. That would break the code that the compiler allowed only because the getter was backed by a field. We'd lose getter/field symmetry, which is the entire reason for having getters to begin with. Dart does not allow you to distinguish between a getter written using We'd have to introduce something like constant instance fields, fields that are like final fields, but are usable in Adding a language feature just to allow accessing fields of const objects has not been a priority so far. It doesn't happen often enough. |
This is exactly what I had hoped for when I clicked "watch all issues on repository" a few months back :) Thank you both for the detailed write-up, my interest in stable getters has gone up considerably now. |
If a class defines a
const
constructor, it can be created either asconst
or as regular internallyfinal
object.Should this
const-in-const-context
notion not apply to the parameters as well?Take e.g.:
Here
const v = 1; const CanConst(1)
works but(int v) => const CanConst(v);
does not.=> parameter must be constant when used in constant context.
Where as
const v = 1; CanConst(1)
and(int v) => CanConst(v);
both work.=> parameter must not be constant when used outside constant context.
==> parameter behaves like it is
const-in-const-context
Since the parameter is currently not
const-in-const-context
, we get e.g. the following:Which I think should be warned about in
const CanNotConstContainer(this.cnc)
sinceCanNotConst
will never be able to beconst
.Further, we get counter-intuitive (at least to me) behaviour with trying to make classes constant:
Starting with this non-constant class:
We can make the default constructor
const
sinceMyOtherClass
has a const constructor:However, we cant do:
Since the
const
part of the constructor is only used inconst
context, I had the following mental model:Used in
const
context.Used outside of
const
context.P.S.
I recently assumed that fields of an object created via
const
would beconst
as well, which might be related (even if only in my wrong mental model)(#1868 (comment))
The text was updated successfully, but these errors were encountered: