New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 23473: Improvement: Implement single-argument __traits(getOverloads) form. #14631
base: master
Are you sure you want to change the base?
Issue 23473: Improvement: Implement single-argument __traits(getOverloads) form. #14631
Conversation
Thanks for your pull request and interest in making D better, @FeepingCreature! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + dmd#14631" |
bef8ba2
to
8eb9091
Compare
The behavior in the presence of imported overload sets needs to be defined: // a.d
void foo(int);
void foo(string);
// b.d
void foo();
// c.d
import a;
import b;
__traits(getOverloads, foo); // which one?! |
Also, please add "Fix" in the commit message so that the bot auto-closes the issue if this gets merged. |
@FeepingCreature I've tested a bit locally and this seems to be a problem only if the overload set is defined and used in the same module. For example, this works: // b.d
void first(int) { }
// c.d
void second(string) { }
// a.d
import b : foo = first;
import c : foo = second;
// d.d
import a;
void main()
{
alias overloads = __traits(getOverloads, a, "foo");
static foreach(ov; overloads)
pragma(msg, __traits(identifier, ov));
} So if you know where the overload set is defined, things work correctly. In this light, I would argue that we do not need this addition. |
So a template that takes an I think you're falling into C++ thinking a bit here. You shouldn't ask "can we do this with what we already have", but "is this good or is this bad?" I think doing it that way is bad. Nobody looks at "Yeah you can't write |
I agree, it's just that I wonder if this form of I guess the question is: how often do we find ourselves in the situation presented in the bug report. If it happens a lot, then I guess a case could be made for this addition. If it's not that often, then maybe we should just stick with what we have and accept that you need to jump through some extra loops in some rare cases. |
To give some extra context, my usecase is that I want to pass an overload set to the alias parameter of a function and apply it to sumtype.match "as if it were a bunch of lambdas I've passed individually". Basically, the goal is to use In effect, this will look like This is how the code looks right now:
And repeat. Workable, but elegant by no means whatsoever. That said, my argument is less "I can't do that unless I have this feature." I don't need need this feature. My argument is this: an overload just is a symbol. Why does |
Overload set rules are complicated, and because of that and the fact that I didn't come up with the ones for D, I'll have to leave this one to @WalterBright |
Slightly off-topic: I'm not sure that replacing D's built-in overload selection with |
Offtopic, but I mean, that's something that can be fixed in code - if there's a way to access the overload members. |
I just ran into an issue where I'd be able to make really good use of this, again. What's blocking it, what can I do? |
A rebase and a response from @WalterBright by the looks of it, failing the latter, just the former. I would have expected a larger test case, but I suppose the feature is narrow enough that that is all that is required to cover it. |
8eb9091
to
ba2cf65
Compare
4798ade
to
bee25dd
Compare
If you have any ideas for more tests, I'm all ears. It really is fairly simple. Rebased. It's a bit annoying of a diff because it adds an indentation. I would like some feedback from Walter because I don't think the stylistic approach is very good. |
@FeepingCreature Please add this test case: // a.d
void foo(int);
void foo(string);
// b.d
void foo();
// c.d
import a;
import b;
__traits(getOverloads, foo); // which one?! And also clarify the behavior. |
Thanks a bunch! That testcase actually surfaced an issue; which to fix I had to adjust the behavior of D overload resolution in general, so now I really want Walter to review before merging. Shouldn't have a user-visible impact, but let's see what the testsuite says. |
|
||
static assert(__traits(compiles, __traits(getOverloads, baz)[0]())); | ||
static assert(__traits(compiles, __traits(getOverloads, baz)[1](int.init))); | ||
static assert(__traits(compiles, __traits(getOverloads, baz)[2](string.init))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be an error? Why is the first import chosen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not. __traits(getOverloads, baz)
is a tuple of three functions; two from the first import, one from the second. (The order is a bit weird, but shouldn't matter.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it get its order? Is it each overload as they are added to the OverloadSet
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, order of iteration in overloadApplyRecurse
.
e.deprecation("`traits(getVirtualFunctions)` is deprecated. Use `traits(getVirtualMethods)` instead"); | ||
} | ||
uint errors = global.errors; | ||
Expression eorig = ex; | ||
ex = ex.expressionSemantic(scx); | ||
if (errors < global.errors) | ||
e.error("`%s` cannot be resolved", eorig.toChars()); | ||
|
||
/* Create tuple of functions of ex | ||
*/ | ||
auto exps = new Expressions(); | ||
Dsymbol f; | ||
if (auto ve = ex.isVarExp) | ||
{ | ||
if (ve.var.isFuncDeclaration() || ve.var.isOverDeclaration()) | ||
f = ve.var; | ||
ex = null; | ||
} | ||
else if (auto dve = ex.isDotVarExp) | ||
{ | ||
if (dve.var.isFuncDeclaration() || dve.var.isOverDeclaration()) | ||
f = dve.var; | ||
if (dve.e1.op == EXP.dotType || dve.e1.op == EXP.this_) | ||
if (e.ident == Id.getVirtualFunctions) | ||
{ | ||
// @@@DEPRECATED2.121@@@ | ||
// Deprecated in 2.101 - Can be removed from 2.121 | ||
e.deprecation( | ||
"`traits(getVirtualFunctions)` is deprecated. Use `traits(getVirtualMethods)` instead"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did this really need to be broken up into two lines?
Just viewing with whitespace off, having nothing technical to say, so just making suggestions to make the total number of changes down to a minimum.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D code style says hard 120 limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷
(master) $ wc -L compiler/src/dmd/*.d | sort -hr
340 compiler/src/dmd/dmangle.d
271 compiler/src/dmd/dinterpret.d
269 compiler/src/dmd/func.d
266 compiler/src/dmd/dcast.d
265 compiler/src/dmd/expressionsem.d
257 compiler/src/dmd/toctype.d
235 compiler/src/dmd/dtemplate.d
230 compiler/src/dmd/dmacro.d
223 compiler/src/dmd/ctfeexpr.d
211 compiler/src/dmd/doc.d
204 compiler/src/dmd/dsymbolsem.d
--- and 70 more files where that came from ---
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not great lol, but I figured I shouldn't actively make it worse.
cc @WalterBright @atilaneves as these is a new language addition. |
ping (Sure would have been good to have this now that the |
__traits(getOverloads) takes an overload symbol | ||
|
||
`__traits(getOverloads)` can now be invoked with | ||
a single parameter. This parameter must be an overload set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused about what this means. Each overload of foo in a different module forms a different overload set:
module a;
void foo();
void foo(int);
module b;
void foo();
There are two overload sets here, the one for a
and one for b
. How is this going to work with this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overload sets can also have as members other overload sets.
I can't tell from the bug report, the documentation, or the comments, what this is going to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're in a module where you see both a
and b
, and you specify foo
, you consider all three as valid overloads. It should use the lexical scope of the identifier token. Ie. the foo
in __traits(getOverloads, foo)
behaves just the same as if you instantiated a template template templ(alias foo) {}
with templ!foo
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An overload set is something specific in D, and is not just an aggregation of all the instances of a name that is in scope. I.e. it is not a tuple.
It cannot behave the same, as templ!foo
will give an error if more than one foo
exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused, because this works:
void foo(int a) {}
void foo() {}
void bar(alias x)() { x(5); x(); }
void main() {
bar!foo();
}
This also works if you split the foo
s into two separate modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like this is not retrieving overload sets at all, it is retrieving all the members of all the overload sets with the given name. Is this correct?
I'm not sure why this would be useful, as the point of having overload sets is to not confuse the members of one with the members of another, i.e. the anti-hijacking overload rules. With this change, functions that just happen to have the same name but are in different modules with utterly different uses will be lumped together.
__traits(getOverloads) takes an overload symbol | ||
|
||
`__traits(getOverloads)` can now be invoked with | ||
a single parameter. This parameter must be an overload set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overload sets can also have as members other overload sets.
I can't tell from the bug report, the documentation, or the comments, what this is going to do.
compiler/src/dmd/traits.d
Outdated
if (dim != 2 && !(dim == 3 && e.ident == Id.getOverloads)) | ||
if (e.ident == Id.getOverloads) | ||
{ | ||
if (dim != 1 && dim != 2 && dim != 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dim < 1 || 3 < dim
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
compiler/src/dmd/func.d
Outdated
@@ -3008,7 +3008,7 @@ extern (D) int overloadApply(Dsymbol fstart, scope int delegate(Dsymbol) dg, Sco | |||
else if (auto os = d.isOverloadSet()) | |||
{ | |||
foreach (ds; os.a) | |||
if (int r = dg(ds)) | |||
if (int r = overloadApplyRecurse(ds, dg, sc)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this change going to impact other uses of this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, maybe? I don't understand the overload logic here. It seems that this function could stuff an entire overload set into the callback, which then presumably split it up further. I suspect there isn't a semantic difference here between "the callback splits up an overload set" and "the callback is called multiple times with the functions in an overload set". And the fact that buildkite is green seems to bear this out.
(This change is the primary reason I wanted your eyes on this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why this would be useful, as the point of having overload sets is to not confuse the members of one with the members of another, i.e. the anti-hijacking overload rules. With this change, functions that just happen to have the same name but are in different modules with utterly different uses will be lumped together.
Oh, is that how it works? If you have three modules, a, b, c
, where c
imports a
and b
, and all three have a foo
respective, then there's three overload sets for "foo"? Do these all resolve on a lookup to "foo" and then get filtered down?
My basis for the model here is if you call template!foo
generally, that has to end up with one indistinguishable overload set... right? Or does it result in one symbol consisting of three overload sets in a next
chain? (My understanding of DMD's lookup logic is very shallow.)
/* Create tuple of functions of ex | ||
*/ | ||
auto exps = new Expressions(); | ||
Dsymbol f; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happened to f
? It's used later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's set to null
way above and filled in directly with the parameter symbol in the one-argument case.
compiler/src/dmd/traits.d
Outdated
e.error("`%s` cannot be resolved", eorig.toChars()); | ||
|
||
if (e.ident == Id.getVirtualFunctions) | ||
if (e.ident != Id.getOverloads || dim != 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use e.ident == Id.getVirtualFunctions || e.ident == Id.getVirtualMethods
instead, as it's much more robust than the nots. Even better, give the deprecation message earlier and get rid of the getVirtualFunctions case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusing in the diff, because the indentation changes. The deprecation check is unrelated to the if
: I've rewritten it a bit to avoid !=
regardless. The single-parameter getOverloads
gets to skip this entire block because it already gets f
directly.
return dimError(2); | ||
|
||
bool includeTemplates = false; | ||
Expression ex = null; | ||
Dsymbol sym = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is sym
declared here, when its first use is on line 1059?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's filled in the else
branch for the Id.getOverloads && dim == 1
test.
The thing I'm aiming for is: "Given a symbol (usually resolved from an identifier), retrieve all the concretely-callable things (functions, aliases, structs with I thought this was what |
It was not recursively expanding overload sets. For example, if this were pulled as it is, then getOverloads would provide a tuple of overloads with the overload sets information removed, meaning it could not be used as a substitute for overload resolution. The anti-hijacking information will be lost along with the overload sets. This is why I question the utility of the proposed feature. It will lump together functions that were never designed to be overloaded with each other, they just happened to have the same name. |
But if they're referenced by name in the current module, aren't they by definition overloaded with each other (just by virtue of having the same name)? That's how I've been using them. I don't understand when you'd want to use Example, paraphrased from actual production code:
|
|
Ah, I see. I wasn't aware of the additional wrinkle that exactly one set had to match. That makes the design make a lot more sense! So should/could
As long as I can eventually get at all the leaves, it should do for my purposes. |
21546dc
to
609adc0
Compare
609adc0
to
455bccc
Compare
Removed the change in This is a bit iffy: it only affects implicit overloads created by importing the same name multiple times. Explicit overloads, created with (Though I'm honestly fine with this hack, because I don't think anyone else understands alias/overload management either.) |
ping |
I don't understand what |
Spoken frankly: it seems to me you are giving a lot of priority to this way of avoiding overload collisions, whereas from my point of view the originating module of a symbol is basically never relevant. I don't think this has ever mattered to me, and honestly I'm not sure it's ever mattered to anyone else - observe Buildkite remaining green after the For background, the motivation is about So when I saw
and so on. Overloads cleanly, and I can just use I don't want to emphasize my usecase too heavily. This is meant more as an illustrative example for why someone could want this. The reason I actually went ahead and did the PR is: if you see a trait called If you want overload behavior, you just call it! Why would you be taking the overload set apart at all if you weren't interested in the properties of its leaves? |
Buildkite is obviously not going to fail by you removing a way that code could fail to type check. This does not show what you say it shows. In any case, is it not confusing to have a |
Ah yeah, good point, sorry. As per the test, |
https://issues.dlang.org/show_bug.cgi?id=23473
Rationale:
At the moment, when looking at an overload set passed via an alias parameter to a template, there is no way of extracting the individual functions making up the overload set. In the ordinary case, you can use a hack like
__traits(getOverloads, __traits(parent, sym), __traits(identifier, sym))
, but this fails as soon as the overload set contains alias renames or other modules. But__traits(getOverloads)
internally looks at a symbol anyways, so we just redefine the single-argument form (currently an error) to pass that symbol directly.Should I make a spec PR first?