Compilation of record-wrapped array fails when assignment to real is added

The code below compiles and runs nicely (Chapel 2.5), and does what it is meant to.
However, if I uncomment the three procedures meant to include assignment to real, compilation fails with the message

overeq.chpl:35: In function 'main':
overeq.chpl:49: error: an init= initializing 'narray(1)' from 'narray(1)' is missing
overeq.chpl:1: note: expected because assignment is defined here between these two types

Any hints of how to uncomment and make the original code work will be welcome. Thanks.

record narray {
   param ran = 1;                  // the rank
   var dom: domain(ran) = {1..1};  // the domain                              
   var arr: [dom] real = 0.0;      // the array                                  
   proc size: int {                // the size of a narray
      return dom.size;
   }
   inline proc ref this(in k: int...?n) ref {     // access arr[k]               
      return arr[k];
   }
   iter ref these() ref {               // iterate over the whole narray         
      for i in dom do {
         yield arr[i];
      }
   }
   iter ref these(                      // iterate over several ranges           
      in rak: range(int)...?r
      ) ref {  
      for k in zip(rak) do {
         yield arr[k];
      }
   }
   /*
   proc init=(const in a: real) {
      this.arr = a;
   }
   operator :(a: real, type t: narray) {
      var v: narray = a;
      return v;
   }
   operator =(ref lhs: narray, const in a: real) {
      lhs.arr = a;
   }
   */
}

proc main() {
   writeln(isGeneric(narray));
   writeln(isGeneric(narray(1)));
   var a: narray(2);
   var b: narray(1);
   var c: narray(1);
   var z: [1..5] narray(1);
   a = new narray(2,{1..10,1..5});
   b = new narray(1,{1..10});
   c = new narray(1,{1..3});
   writeln(a);
   writeln(b);
   writeln(c);
   for i in 1..5 do {
      z[i] = new narray(1,{1..i});
      z[i].arr = 1..i ;
   }
   for i in 1..5 do {
      writeln(z[i]);
   }
}

Hi Nelson —

What's happening here is that, currently, the Chapel compiler will only create default init= and operator = implementations for records if the user doesn't supply any overloads of those methods themselves. Once you have, it assumes "This type author wants to support these specific initializations/assignments (in your case, from a real), so I assume they want more explicit control over what the record can and cannot do" and as a result, it no longer creates the methods by default.

At present, the way to make your code work again is to add your own overloads for these record-to-record cases. For example, I did this by adding these overloads to your type:

   proc init=(const in rhs: narray(?)) {
     this.ran = rhs.ran;
     this.dom = rhs.dom;
     this.arr = rhs.arr;
   }
   operator =(ref lhs: narray, rhs: narray(lhs.ran)) {
     lhs.dom = rhs.dom;
     lhs.arr = rhs.arr;
   }

as seen here on ATO.

An intention we have for the future, which we haven't worked on designing yet, is to provide a way to say "Compiler, please provide the default overloads for these types in addition to the ones I've provided here." Basically, a way to opt back into having the defaults provided without writing them manually. Unfortunately, that does not exist yet today because it would save you a bit of typing in this case.

I also think we could arguably improve the error message in cases like this. For example, if we can't find a t-to-t assignment or init= and we see that the user defined overloads, we could provide an explanatory message like the one at the outset of this message explaining that the compiler didn't provide them due to the user overloads. If you would like to request that, please feel free to open an issue on our GitHub requesting it.

One other thing I want to mention, which could bite you down the line, is that by using the type signature narray rather than narray(?) in your argument lists, you are constraining them to the default generic instantiation of narray (which is to say narray(ran=1)). As a result, your overloads won't work for an narray(ran=2) case like a = 2.5;. You should be able to make them more general by using narray(?) as the type constraint, which says "take any instantiation of narray()" (in this case, an narray with any value of param ran). In the methods I wrote above, you can see that I did this for the init= but for the = operator, constrained the RHS to have the same ran value as the LHS (since an assignment cannot change the type—in this case the ran value—of the LHS).

Hope this is helpful, and please let us know if you have additional questions,
-Brad

Great Brad: thanks! Now everything works. I had suspected the need for an explicit
init=(...), bumped my head around a few times, but could not make it work.

The extra code is not a problem (in my opinion), but an example of this situation in the documentation might be helpful (just thinking).

Cheers,

Nelson