New Issue: Default 'param' arguments in dynamic dispatch don't behave correctly

17683, "bradcray", "Default 'param' arguments in dynamic dispatch don't behave correctly", "2021-05-12T17:15:28Z"

[This issue is spun off from #15164 to focus on a specific sub-case of it]

Summary of Problem

When a class hierarchy has an overridden method that takes a param argument and it has a default value, and a call is made to that routine relying on the default value, the value used for the default is based on the receiver object's static, rather than dynamic, type. This is inconsistent with what we do for non-param arguments, and seems surprising/intuitive/wrong.

For example, in:

class Parent {
  proc method(param arg:int = 10) {
    writeln("in Parent.method arg=", arg);
  }
}

class Child : Parent {
  override proc method(param arg:int = 20) {
    writeln("in Child.method arg=", arg);
  }
}

var x:Parent = new Child();
x.method();

we'd call Child.method() but passing in 10 as the default argument

The source of this problem is that at some point we switched to having default arguments computed at the callsite, and implemented them using methods on the original classes that computed the defaults. Yet for a param argument, these default values are generated by param methods, which do not support dynamic dispatch by nature.

In virtual dispatch and default param arguments · Issue #15164 · chapel-lang/chapel · GitHub, @mppf suggests that we could implement this by returning to our previous "specialize methods with default arguments" approach, but only for param and type arguments, not value arguments. For the example above, the result would be that we'd have two overloads of method per class, one that took a single param argument with no default, and one that took no arguments and used the default argument corresponding to that class.

Short of implementing that solution, generating an error for this case would be attractive so that people are at least alerted to the fact that it's a pattern that doesn't work as expected / could generate surprising behavior. Doing so would require looking at the class hierarchy, because for a case that didn't dynamically dispatch like:

class C {
  proc foo(param x: int = 10) { ... }
}

we wouldn't want to generate the error (and I'm fairly certain we rely on a number of cases like this).

Steps to Reproduce

Associated Future Test(s):
test/functions/default-arguments/default-argument-class-override3a.chpl #17653

Configuration Information

  • Output of chpl --version: chpl version 1.25.0 pre-release (c17dba0db3)