0

I am developing a tool that generates dart code and I am facing this runtime error when executing a program having a circular dependency.

file.dart:xx:xx: Error: Constant evaluation error:
    this.b = const B(),,
                   ^
file:xx:xx: Context: Constant expression depends on itself.
    this. A = const A(),

Here is a simplification of the program I am executing:

class A {
  final B b;
  const A({
    this.b = const B(),
  });
}

class B {
  final A a;
  const B({
    this.a = const A(),
  });
}

As you can see there is a circular dependency between A and B.

I tried dropping the const keyword as:

class A {
  final B b;
  A({
    this.b = B(),
  });
}

class B {
  final A a;
  B({
    this.a = A(),
  });
}

But instead, I am getting a compile time error:

The default value of an optional parameter must be constant. dart(non_constant_default_value)

Do you have an idea how to handle this type of issue? Or is there an equivalent of forwardref in Dart?

Edit

At the current state of dart, it is not possible to avoid this circular dependency. I ended restricting this case. If the input has a circular dependency, it is normal that the output will have it.

Red Root
  • 51
  • 8
  • I am curious *dropping the const keyword* why? – Md. Yeasin Sheikh Mar 27 '23 at 22:54
  • I tried dropping the const keyword to break the circular dependency. The error is happening because dart is eagerly instantiating B to complete the construction of A, and so on. If I am able to drop const, the process won't be eager fixing the issue (in my opinion). – Red Root Mar 27 '23 at 23:03
  • I am reading [In this case you need to create a third project "C" which contains the classes that both A and B depend on so they no longer depend on each other](https://stackoverflow.com/a/38042191/10157127), might help you – Md. Yeasin Sheikh Mar 27 '23 at 23:08
  • Thank you Yeasin, I am checking that ! – Red Root Mar 27 '23 at 23:12

2 Answers2

2

Your approaches don't work because when you construct A(), it will construct a B() which will construct A()which will construct B(), ad infinitum. You need to break the infinite recursion loop.

If you want a single instance of A and a single instance of B to have references to each other, you will need to have one of them pass a reference to itself instead of letting the other constructor create a new instance of the initial type. You can't pass this in an initializer list (this is not yet valid at that point), so it must be done in the constructor body, and the initialized final members will need to be late. The constructor body also means that both classes cannot have const constructors.

If you want const constructors because you want to have A() and B() as default arguments, the typical approach is to use null as a default.

class A {
  late final B b;
  A({B? b}) {
    this.b = b ?? B(a: this);
  }
}

class B {
  late final A a;
  B({A? a}) {
    this.a = a ?? A(b: this);
  }
}
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
0

How about using factory constructors?

class A {
  final B b;
  
  factory A() => A._(B());
  
  A._(this.b);
}

class B {
  final A a;
  
  factory B() => B._(A());
  
  B._(this.a);
}