-
-
Notifications
You must be signed in to change notification settings - Fork 608
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
Fix issue 18919 - __FILE__ and __LINE__ should work when used in defa… #15968
Conversation
|
Thanks for your pull request and interest in making D better, @tim-dlang! 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#15968" |
8c6bce6
to
3d8835c
Compare
| size_t line; | ||
| } | ||
|
|
||
| void func2(Loc loc = Loc(__FILE__, __LINE__)) |
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.
as a code comment you should mention that this case in particular did not work
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.
The changelog entry needs to be more accurate.
13a2efd
to
5e1f5f2
Compare
|
The new test was not portable, because the output contained path separators and size_t was different. Now the output is more portable. I also fixed another edge case: CatExp is lowered to |
5e1f5f2
to
480f2cb
Compare
|
Pipeline "Windows_VisualD_LDC x86-mscoff_MinGW" seems to not support %zd for printf, so the test now only uses %d and int for line numbers. A memory allocation failure on "Windows_VisualD_LDC x64_Debug" seems to also randomly happen on other PRs. I have created an issue for that: https://issues.dlang.org/show_bug.cgi?id=24309 |
|
So cool, thanks! |
|
What happens now if you do |
compiler/src/dmd/expressionsem.d
Outdated
| const saveInDefaultArg = sc.inDefaultArg; | ||
| sc.inDefaultArg = false; | ||
|
|
||
| auto e = compileIt(exp); | ||
| sc.inDefaultArg = saveInDefaultArg; |
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 clear to me what's going on here
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 relevant for the following test:
void func(string expr = mixin("\"expr2=" ~ __MODULE__ ~ "\""))
{}
The mixin expression is inside a default argument, so sc.inDefaultArg is true. The code now sets sc.inDefaultArg temporarily to false, so the inside of the expression can be evaluated at compile time.
It's the same problem as for the example by @dkorpel with a template, but I only fixed it for a mixin expression and not templates.
| import imports.issue18919b; | ||
|
|
||
| void main() | ||
| { |
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.
Could this be a compilable? (They're much faster).
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.
No, the test checks the output of the program at runtime und RUN_OUTPUT.
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.
A lot of the compilables use a static assert to check values.
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.
The values are not always available at compile time. Only func5 and func6 pass the values as template parameters, so they could be checked with static assert. Most of the test functions pass the values using runtime parameters, so a test at runtime is still necessary.
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.
Maybe it would be possible to run everything at CTFE. Then it could be in compilable. But then it would not test that for example __FILE__.ptr is correctly handled by the backend.
Unfortunately that is now an error: I did not check this previously. It would be possible to change it, so it still compiles, but it would be the line at the template instantiation and not the call site. |
|
If you aren't going to "fix" the template thing, then the current behavior should be preserved. It should not become an error. I would say fixing template parameters to be from the call site is not a requirement for this issue. But certainly, we can't break existing code. |
58e9f2d
to
56a42ed
Compare
|
The example |
|
The fact that we have a special case in the language for I think a better way to support these use-cases would be to add a general-purpose mechanism for evaluating default arguments at the call site, which is not tied specifically to the presence of the |
I don't disagree, but a couple questions here:
I don't want perfect to be the enemy of good here. We can make the code look nice later, right? This is a real use case that can immediately be useful, since 99.999% of people create exceptions without specifying explicitly the line and file, they let the default arguments do their thing. We can easily deprecate the size_t and string parameters, and use some LOC struct to fix this, and then it secures infinitely more the intent of the user when building exceptions using explicit file/line parameters. I would hate to delay it because the implementation details are not perfect. |
I don't think it's a huge challenge technically, but it probably requires a DIP
Yes, but then we're stuck with the special case. That said, there's a case to be made that having |
|
Currently, the semantic analysis for default arguments runs once at the declaration site. Later the AST for a default argument is copied to the call site and special tokens are replaced. This is not only done for the special tokens, but also for code like The compiler has another special case before this PR for function A more general solution could be to run the semantic analysis for default arguments not just once, but for every call of a function. Symbol resolution should still be done in the context of the function declaration. Templates could then be instantiated with different values for special tokens as template arguments. This could also be more maintainable, because resolveLoc would not be needed anymore. Currently, resolveLoc would need to be updated for some changes to AST classes. On the other hand, running semantic analysis multiple times could be slower. |
This indeed sounds like a more general and principled approach to me.
This worries me as well. That's why I think should try out this approach and measure the performance impact. And then take a decision based on the data. |
I did a first test of doing the semantic analysis at the call site, but it is not complete, and this PR is unchanged. I tested it by compiling std.regex with unittests enabled. Doing the semantic analysis only at the call site increased the runtime by roughly 0.6%. Unfortunately the semantic analysis also needs to be done at the declaration site, so error messages can be shown for functions never used. Doing the semantic analysis both at the declaration site and the call site solves this, but then the runtime of DMD increases by roughly 4.9%. Another problem is memory consumption: The scope of the function needs to remain valid, so it can be used by the semantic analysis at the call site. In my test DMD consumed 2.1% more memory. It was only a first test and there could be possible optimizations, or I made mistakes, but I think doing semantic analysis at the call site would not be worth it with those increases in runtime and memory consumption. |
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.
As others have mentioned, this isn't particularly ideal. I also wish we could re-use existing visitor code for this. However, this has immediate value, and I agree with Steven:
I would hate to delay it because the implementation details are not perfect.
So I think it's good to get this in.
| return exp; | ||
| } | ||
|
|
||
| Expression visitArray(ArrayExp exp) |
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 function is not covered by tests
compiler/src/dmd/dtemplate.d
Outdated
| @@ -5625,7 +5625,11 @@ extern (C++) final class TemplateValueParameter : TemplateParameter | |||
| if (e) | |||
| { | |||
| e = e.syntaxCopy(); | |||
| if ((e = e.expressionSemantic(sc)) is null) | |||
| const saveInDefaultArg = sc.inDefaultArg; | |||
| sc.inDefaultArg = true; | |||
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 'save and restore' pattern is risky, since sc may be referenced by siblings. It's safer to push and pop a scope, see: #15434 (comment)
Although in these cases, I don't think it's possible for there to be expression siblings that aren't default args.
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.
| * Parses default argument initializer expression that is an assign expression, | ||
| * with special handling for __FILE__, __FILE_DIR__, __LINE__, __MODULE__, __FUNCTION__, and __PRETTY_FUNCTION__. | ||
| */ | ||
| private AST.Expression parseDefaultInitExp() |
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 love this removal
|
|
||
| case TOK.line: | ||
| e = new AST.IntegerExp(loc, loc.linnum, AST.Type.tint32); |
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.
And I love how the tokens aren't resolved in the parser anymore, which is useful for dmd as a library
…ult argument expressions The parser now always creates AST nodes for default init expressions like __FILE__. They are replaced in resolveLoc. Variable inDefaultArg in Scope is used, so the nodes are not replaced too early.
|
I have added a test for ArrayExp and modified the code changing inDefaultArg, so it uses a new scope. Unfortunately this change first triggered an assert in I added the call |
|
This PR introduced a regression: https://issues.dlang.org/show_bug.cgi?id=24560 Fixed in: #16519 |
…ult argument expressions
The parser now always creates AST nodes for default init expressions like
__FILE__. They are replaced in resolveLoc. Variable inDefaultArg in Scope is used, so the nodes are not replaced too early.