This is an edge case of #16710
Say we have a module structure as follows, where a parent type and a child type are defined in one module, and a method on the parent type is defined in another module:
module definesClass {
class Parent {
var a: int;
}
class Child: Parent {
var b: int;
}
}
module definesTertiary {
use definesClass;
proc Parent.tertiaryMethod() {
writeln("In inherited tertiary method");
}
}
Under what use and import scenarios should tertiaryMethod be available to instances of Child?
Case 1a:
Should listing the Child type in an only list or import list on the module enable it to be found? E.g.
use definesClass;
use definesTertiary only Child; // Should this bring in tertiaryMethod for instances of Child?
var x = new owned Child(2, 3);
x.tertiaryMethod(); // Should this resolve?
Case 1b:
Should listing the Child type in an except list on the module prevent it from being found? E.g.
use definesClass;
use definesTertiary except Child; // Should this prevent calls to tertiaryMethod for instances of Child?
var x = new owned Child(2, 3);
x.tertiaryMethod(); // Should this resolve?
Case 2a:
Should listing the Parent type in an only list or import list on the module enable it to be found? E.g.
use definesClass;
use definesTertiary only Parent; // Should this bring in tertiaryMethod for instances of Child?
var x = new owned Child(2, 3);
x.tertiaryMethod(); // Should this resolve?
Case 2b:
Should listing the Parent type in an except list on the module prevent it from being found? E.g.
use definesClass;
use definesTertiary except Parent; // Should this prevent calls to tertiaryMethod for instances of Child?
var x = new owned Child(2, 3);
x.tertiaryMethod(); // Should this resolve?
These scenarios interplay in interesting ways in my mind. I can see arguing for both sides on each of the cases, and find myself a bit inconsistent in the reasoning since the method simultaneously is only defined on Parent but impacts both Parent and Child's behavior.
@mppf @bradcray and @vasslitvinov - I'm curious about your thoughts on these scenarios
Interesting question. My quick response is that 1a should not bring it in, 1b should, 2a should, and 2b should not.
The cases that these examples make me worry about are ones in which dynamic dispatch tables might differ from one calling context to the next based on which tertiary methods are or are not visible (say, one in which the original type defines an overrideable method and the child class does or doesn't override it as a tertiary method). That concern makes me wonder whether we should make it illegal to define tertiary methods that would change the dynamic dispatch behavior of a class (or some variation on that constraint). Maybe we already handle such cases better than I'm fearing, however, and it's not an issue (?).
This concern doesn't apply to this case, however, because the method is either statically visible or it isn't, so there's no need to have a different dispatch table based on whether or not it is.
I thought about this before reading @bradcray's answer and came to the same conclusion. Should it resolve? 1a no 1b yes 2a yes 2b no.
My reasoning is that the only SomeType feature should be straightforward in that it looks for things that are methods on SomeType. In the example, we have proc Parent.tertiaryMethod and so only Child should not bring it in (because it is a method on Parent not on Child).
While there is a difference between subtyping/inheritance and implicit conversion, I think the situation where a Child has access to the Parent's method is like implicit conversion. I wouldn't expect e.g. module M { proc int(8).double() { ... } } to become available if I use M only int because while we can convert from int(8) to int, they are not the same type.
I think @bradcray's question about dynamic dispatch is interesting. I was going to post an example here with my thoughts but then decided it would be off-topic for this issue. So I created #16854 to discuss that question.
Thank for your thoughts! I think the only thing that gives me a little pause is that it seems like the purpose behind writing except Child is to say "I don't want Child's behavior to be impacted by using this scope", but Child's behavior is still impacted. However, I think the definition Michael put forward,
[T]he
only SomeTypefeature should be straightforward in that it looks for things that are methods onSomeType.
gives a reasonable explanation and a reasonable solution for the user if that was their intention (namely, they should exclude any type in that scope which defines methods Child could use).
In addition to the dynamic dispatch discussion, I'm also curious about the impact of this on forwarding and generic instantiations and will open issues for those as well
it seems like the purpose behind writing except Child is to say "I don't want Child's behavior to be impacted by using this scope"
If you think of it more as "I don't want to bring in any functionality that's specific to Child then it's not so bad, though, right?
I think this is arguably clearer in an import world where you'd have to say import definesTertiary.Parent; and then, assuming you understand the relationship between Parent and Child, it wouldn't be surprising that Child was affected. Put another way, I think of use definesTertiary except Child; as being an odd way of saying someone doesn't want Child affected for this module (since it reads to me as "bring in everything defined in definesTertiary but I'm too lazy to say what that is or to exclude methods on Parent).