language icon indicating copy to clipboard operation
language copied to clipboard

Allow a `const` record entry to be assignable to a `const`

Open rubenferreira97 opened this issue 2 years ago • 5 comments

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);

rubenferreira97 avatar Apr 17 '23 21:04 rubenferreira97

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.

munificent avatar Apr 17 '23 23:04 munificent

Note the overlap with https://github.com/dart-lang/language/issues/2219#issuecomment-1490026741.

eernstg avatar Apr 18 '23 09:04 eernstg

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.

ds84182 avatar Jun 19 '23 06:06 ds84182

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 int operations, possibly all of them (int.sign, int.abs(), int.isEven, etc.).
  • A large number of String operations (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)

lrhn avatar Jun 19 '23 21:06 lrhn

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;

mmcdon20 avatar Apr 28 '25 20:04 mmcdon20