chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.78k stars 420 forks source link

Nilability ergonomics: passing 'nil' to a generic nilable formal #22208

Open benharsh opened 1 year ago

benharsh commented 1 year ago

Currently we cannot pass nil to a nilable, un/managed, but otherwise generic formal. This seems reasonable seeing as nil can't contain the relevant information to instantiate the generic formal properly. For example, consider the following program:

class C {
  type T;
  var x : T;
}

proc foo(arg: unmanaged C?) {
  writeln("foo");
}

proc main() {
  foo(nil);
}

This doesn't work because we can't determine an appropriate instantiation for T. The compiler will issue an error:

foo.chpl:11: In function 'main':
foo.chpl:12: error: unresolved call 'foo(nil)'
foo.chpl:7: note: this candidate did not match: foo(arg: unmanaged C?)
foo.chpl:12: note: because actual argument #1 with type 'nil'
foo.chpl:7: note: is passed to formal 'arg: unmanaged C?'

However reasonable, logical, or practical, it's still a bit annoying to deal with. A workaround is to create a dummy, nilable temporary:

var temp : unmanaged C(nothing)?;
foo(temp);

Can the language do a better job of supporting this case?

If not, can the compiler add a slightly more helpful error message to explain what's going on? Personally, I ran into this and spent an embarrassing amount of time thinking it was a compiler bug before realizing the now-obvious rationale behind this behavior.

bradcray commented 1 year ago

Can the language do a better job of supporting this case?

It seems as though this would be challenging in general—wouldn't the compiler need to understand the generic type well enough to know what a reasonable placeholder would be to pass in to every argument in the type constructor? I suppose in a world where the compiler controls all type constructors, this would be possible, but once we (hopefully) support user-defined type initializers, you could imagine it passing in 0 or 1 for a param value that the type initializer required to be 8, 16, 32, or 64.

For that reason, the error message seems like the safer option to me, with respect to 2.0 and stabilizing the language (where I agree that the current error message doesn't make it at all clear what the problem is).

benharsh commented 1 year ago

From a language-support perspective, I was imagining defining the ability for nil to instantiate a generic nilable type as some sort of proper equivalent to nil.type. But that has its own kind of problems, where a user might need to check both the runtime value and static type of the formal before doing anything useful.

Now that I think of it, another workaround that avoids the temporary (and cluttering the local namespace) is to simply cast nil:

foo(nil:unmanaged C(nothing)?);

Anyways, I'd be perfectly content with improving the error message here.

mppf commented 1 year ago

In particular, perhaps the error message could recommend casting nil to get the intended instantiation type as you did in the workaround foo(nil:unmanaged C(nothing)?);.