Skip to content

Introduce a couple of new potential constants and allow a non-redirecting factory constructor to be constantΒ #4571

@eernstg

Description

@eernstg

This issue is a proposal that constructors like const factory A(int i, String s) => B(i, s); should be supported.

(See also #3356, which is a subset of this proposal.)

The sublanguage of constant expressions in Dart currently does not include abstraction over instance creation for anything other than records. For example:

class A {
  final Record r;
  const A(int i, String s): r = (i ,s); // OK.
}

This is allowed because (i, s) is a potentially constant expression and, the result of substituting the actual arguments for the formal parameters at a call site which is a constant expression is a constant expression (e.g., if we call it using const A(1, "") then there is no compile-time error because the resulting record (1, "") is a constant expression).

However, an instance creation expression like const A(...) cannot contain formal parameters because it can only receive actual arguments which are constant expressions. For example:

class B {
  final A a;
  const B(int i, String s): a = const A(i, s); // Error!
}

It is also an error when const is omitted from const A(i, s) because A(i, s) is not a constant expression, and it is required that every invocation of the constructor B is such that a substitution of the actual arguments for the formal parameters yields constant expressions in every initializer element. In the example, the substitution of an invocation like const B(1, "") yields A(1, ""), and that is not a constant expression.

Proposal

An instance creation C<T1 .. Ts>(a1, .. ak) (where <T1 .. Ts> may be absent) that occurs in a declaration of a constructor k of a class D is a potentially constant expression when each of a1 .. ak is a potentially constant expression.

In Dart without this feature, invocations of a constant constructor k during constant expression evaluation is required to be such that a substitution of the actual arguments for the formal parameters yields constant expressions in every initializer element. This rule is enhanced such that the substitution on an instance creation where not all actual arguments are constant (that is, some of the actual arguments are only potentially constant) will add the modifier const on said instance creation.

This is the step in the example in the previous section where A(i, s) by substitution is turned into A(1, ""). This expression is then turned into const A(1, ""), which is constant.

Similarly, a list literal or set literal is potentially constant if it only contains elements that are potentially constant, and a map literal is potentially constant if it only contains keys and values that are potentially constant.

The substitution rule is further enhanced such that the substitution on a collection literal where not all elements are constant (that is, some of the elements are only potentially constant) will add the modifier const on said collection literal.

For example, we might have an expression <int>[i, j] in the initializer list which is turned into <int>[2, 3] by substitution, and it is then turned into const <int>[2, 3], which is constant.

An error occurs if a constant expression evaluation more than once evaluates an expression which is obtained by substitution and addition of const on the same potentially constant but not constant expression.

That is, a constant expression evaluation cannot recursively evaluate any of the potentially constant expressions that are introduced by this proposal. This prevents constant expression evaluation from entering an infinite loop. If a more permissive rule can be found that still prevents non-termination then we are free to use it, and this will be a non-breaking change.

Finally, a constant factory constructor declaration of the form const factory D(args1) => e; is allowed when e is a constant expression, and when it is a potentially constant expression. In the latter case, an invocation of the constructor is an error unless substitution of the actual arguments for the formal parameters turns e into a constant expression.

Examples

This allows factories to perform a similar kind of abstraction as redirecting generative constructors (that is, they can change the actual argument list). For example:

class const Point(final int x, final int y) {
  const new gen(int both): this(both, both); // A redirecting generative constructor.
  const factory fac(int both) => Point(both, both); // A factory.
}

extension on Point {
  // Extension constructors must be factories.
  const factory fac2(int both) => Point(both, both); // An extension factory.
}

It also allows existing constant constructors to perform actions which were previously not supported:

class A {
  final List<int> list;
  const A(int i, int j): list = [i, j];
}

In a class with a constant constructor, the initializing expression of each instance variable must be a constant expression. This is not true today with [i, j], and const [i, j] is an error because i and j are not constant. However, with this proposal [i, j] is a potentially constant expression and an invocation like const A(2, 3) is checked in two steps: [i, j] is turned into [2, 3] by the substitution, then [2, 3] is turned into const [2, 3] because the original expression [i, j] contains some expressions that are potentially constant and not constant. The result expression const [2, 3] is a constant expression, so the invocation is accepted (that is, it is not an error).

Here is an example where a potential non-termination danger gives rise to a compile-time error during constant expression evaluation:

class A {
  A._();
  const factory(int k) => k <= 0 ? A._() : A(k - 1);
}

void main() {
  A(2); // Not constant, no problem.
  // const A(2); // Compile-time error.
}

The error occurs because the evaluation of const A(2) will give rise to an evaluation of const A(1) which is an evaluation of the expression A(k - 1) after substitution and addition of const, which gives rise to an evaluation of const A(0), which is an evaluation of the expression A(k - 1) after substitution and addition of const. This is the second time, and that is an error.

Versions

  • Nov 21 2025, version 1.1: Corrected the rule that prevents non-termination and filled in more detail about collection literals and substitution checks.

  • Nov 20 2025, version 1.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhanced-constRequests or proposals about enhanced constant expressionsfeatureProposed language feature that solves one or more problemsstatic-extensionsIssues about the static-extensions feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions