Allow a `const` record entry to be assignable to a `const`
import 'package:flutter/material.dart';
class MyColors {
static const Color primary = Color(0xFF007AFF);
static const Color secondary = Color(0xFF6C757D);
}
const primaryClass = MyColors.primary;
const myColors = (
primary: Color(0xFF007AFF),
secondary: Color(0xFF6C757D),
);
const myColors2 = myColors; // works https://github.com/dart-lang/language/issues/2337
const primaryRecord = myColors.primary; // Error: Const variables must be initialized with a constant value.
If a record is const, and its entries must be const, could we get a way to assign it to a constant? Unfortunately I think a const return (const Color get primary) would need to be present on the getter method. Could we under the hood create a const instance and replace it inline? This may break some things.
The previous code would generate something like this:
const myColors = (
primary: const Color(0xFF007AFF);
);
const primaryRecord = const Color(0xFF007AFF); // myColors.primary -> const Color(0xFF007AFF);
In principle, yes, this is a thing we could do. Since records can't be implemented or inherited from, we know that every record field access is effectively non-virtual. If we know that the record the field is being accessed on is constant, then we can know that the field access is itself constant too.
Note the overlap with https://github.com/dart-lang/language/issues/2219#issuecomment-1490026741.
I really wish this was shipped with 3.0. I wanted to write an inline class with a const constructor that accepts records and turns them into a bitflag:
const RelativeBoxConstraints({required Dim2D<bool> flexible}) : _flags = (flexible.width ? 1 : 0) | (flexible.height ? 2 : 0);
Of course the workaround here is to use separate named parameters for width and height but that simply isn't as clean.
As a workaround, I can use operator ==. But that's a horrible solution to this problem.
There is a number of member accesses we can allow at compile-time.
- Record field access is a clear candidate.
- Constant list and map lookup (but only when applied to lists and maps that are created by literals), plus, maybe
length,first,last,isEmpty. - A large number of
intoperations, possibly all of them (int.sign,int.abs(),int.isEven, etc.). - A large number of
Stringoperations (substring(int,int),isEmpty,isNotEmpty).
I'm pretty sure we have a request for "more const expressions" somewhere, I just can't find it. (Damn you, Github search!)
(Before someone says "final fields", then no, a final field is not a promise to not make it a getter in a later version of the same code. Allowing accessing fields, but not getters, would make it one. #299)
This does seem like an obvious fit for const expressions.
const r = (x: 1, y: 2);
const x = r.x, y = r.y;
It might even be possible to allow a const destructuring, but I see this as less essential.
const r = (x: 1, y: 2);
const (:x, :y) = r;