function argument stringification: __traits(getCallerSource, symbol) (variadics work too)#7821
function argument stringification: __traits(getCallerSource, symbol) (variadics work too)#7821timotheecour wants to merge 4 commits intodlang:masterfrom
__traits(getCallerSource, symbol) (variadics work too)#7821Conversation
|
Thanks for your pull request, @timotheecour! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
d25a83e to
e937185
Compare
test/runnable/testargnames.d
Outdated
|
|
||
| // NOTE: __ARG_STRING__ will pretty-print the expression so passing `t+t` | ||
| // or `t + t` won't make a difference: | ||
| // TODO: how to do formatting ignoring with dfmt? |
There was a problem hiding this comment.
See the documentation: https://github.com/dlang-community/dfmt#disabling-formatting
d978ba6 to
cad4d05
Compare
I think we can get there quickly:
|
|
Note that keeping the files in memory may open up other optimizations and features that weren't available before. This allows strings to be used directly from source. This enables better error messages in the semantic phase since the original source is available to pull context from. The current design which frees source files before semantic analysis may have been holding back superior designs for various solutions. I pose this question to the more seasoned dmd developers who may know of different ways this change could be taken advantage of. |
|
One thing that comes to my mind is |
added evaluation (+ how to reproduce) of effect on max memory usage + time, see updated PR description. Seems like 0 effect on time and negligible effect on memory. |
You have file, line, and column information, you can pull this out of the file at any time during the error diagnostic and read the slice it points to. |
|
I think a traits would be better. You seem to have an unrelated change included here also ( |
|
|
|
@ZombineDev was it you? |
|
the change is related to this PR: this PR introduces |
|
Just wanted to mention that a possible enhancement would be to support multiple arguments using void f(int a, int b, string args = __traits(getArgument, a, b)) {writeln("Arguments: ", args);}
f(1, n % 3);
|
de29def to
77a0589
Compare
77a0589 to
1fc314e
Compare
__traits(getSource, symbol)
| modules = new DsymbolTable(); | ||
| } | ||
|
|
||
| extern (C++) void releaseResources() |
There was a problem hiding this comment.
Global comment: Add Ddoc comment for all new functions.
| } | ||
| else | ||
| { | ||
| pragma(inline, true) |
|
|
||
| /*********************************************************** | ||
| */ | ||
| extern (C++) final class ArgStringInitExp : DefaultInitExp |
There was a problem hiding this comment.
What is an ArgStringInitExp? There's no documentation here.
| { | ||
| version(with_debug){ | ||
| import std.conv:text; | ||
| writelnL(file, ":", line); |
| extern(D) | ||
| final void error(string file=__FILE__, int line=__LINE__)(const(char)* format, ...) const | ||
| { | ||
| version(with_debug){ |
|
|
||
| if(auto argexp = arg.isArgStringInitExp()) | ||
| { | ||
| if(argexp.ident is null) |
There was a problem hiding this comment.
Please don't invent a different indentation style. It's 4 columns.
| } | ||
| if(is_variadic_param){ | ||
| arg=argexp.resolveArgStrings(loc, sc, values); | ||
| } else{ |
There was a problem hiding this comment.
please don't invent new formatting styles.
| arg = inlineCopy(arg, sc); | ||
| // __FILE__, __LINE__, __MODULE__, __FUNCTION__, and __PRETTY_FUNCTION__ | ||
| arg = arg.resolveLoc(loc, sc); | ||
|
|
There was a problem hiding this comment.
What does the following big block of code do? I don't have a clue, so I have nothing to compare the code to. Also, it's so large consider breaking it out into a separate function.
| { | ||
| if(argexp.ident is null) | ||
| { | ||
| error(argexp.loc, "argexp.ident null"); |
There was a problem hiding this comment.
This error message refers to compiler internals and will mean nothing to the user.
| enum vararg_name = "_param_"; | ||
| // TODO: define/use startsWith | ||
| auto ident2b=ident2.toString; | ||
| if(ident2b.length>vararg_name.length && ident2b[0..vararg_name.length]==vararg_name) |
There was a problem hiding this comment.
Global comment: Please use a space before and after operators. Please format code like in the rest of the front end.
| } | ||
| } | ||
|
|
||
| // Get source code for `expr` |
There was a problem hiding this comment.
Why is this different from the code in hdrgen.d which turns ASTs back into strings?
| uint linnum; | ||
| uint charnum; | ||
| // 0-based offset from origin in bytes (allows O(1) access) | ||
| uint bytes; |
There was a problem hiding this comment.
Adding this field, since Loc is attached to every AST node, will result in a very large increase in memory consumption.
There was a problem hiding this comment.
An offset should be called offset not bytes.
| { | ||
| // instead of ',' to avoid confusing with charnum if !showColumns | ||
| buf.writeByte(':'); | ||
| buf.print(bytes); |
There was a problem hiding this comment.
With line/column, I am not sure what use there would be for printing file offsets.
|
|
||
| extern (C++) bool equals(ref const(Loc) loc) const | ||
| { | ||
| // TODO: how about: `return (bytes == loc.bytes) && FileName.equals(filename, loc.filename);` ? |
There was a problem hiding this comment.
This implies that bytes is redundant with line, column
| else if (arg == "-vbytes") // https://dlang.org/dmd.html#switch-bytes | ||
| params.showBytes = true; | ||
| else if (arg == "-disposesrc") | ||
| params.disposeSrcContent = true; |
There was a problem hiding this comment.
why is this optional under user control?
| this.trust = TRUST.trusted; | ||
| } | ||
|
|
||
| extern(D) Parameter searchByName(const(char)[] name){ |
There was a problem hiding this comment.
Global comment: Need Ddoc comments for all new functions.
| delegateFunctionPointer, | ||
|
|
||
| // 54 | ||
| // 54 // TODO: instead of these non robust numbers, use `cast(TOK) number` |
There was a problem hiding this comment.
The numbers are there because when I print out a TOK value, in order to figure out which one it is, I have to count the tokens.
| error(e.loc, "TODO"); | ||
| return new ErrorExp(); | ||
| } | ||
| auto ret = new ArgStringInitExp(e.loc); |
There was a problem hiding this comment.
This is allocated here, but is not initialized or used until the end of the function. It should be allocated there. Also, convention is to use result as the return value, not ret.
| return new ErrorExp(); | ||
| } | ||
| auto ret = new ArgStringInitExp(e.loc); | ||
| auto ident_expr=(*e.args)[0]; |
There was a problem hiding this comment.
calling it arg0 would make more sense, especially since the next line verifies that it is a type, not an identifier.
| auto ident_expr=(*e.args)[0]; | ||
| auto t=isType(ident_expr); | ||
| if(!t){ | ||
| error(e.loc, "TODO.2"); |
There was a problem hiding this comment.
Internal errors should use assert.
| // TODO: lookup? | ||
| auto id = Identifier.idPool(name, name.strlen); | ||
| ret.setIdent(id); | ||
| ret.exp=e; |
There was a problem hiding this comment.
Perhaps ArgStringInitExp should have a constructor that takes these two expressions as arguments.
|
|
||
| if (e.ident == Id.getCallerSource) | ||
| { | ||
| e.error("`%s` unexpected in this context", Id.getCallerSource); |
There was a problem hiding this comment.
Should this be an assert instead?
| string logSimple(T...)(T a, string[T.length] names = __traits(getCallerSource, a)) | ||
| { | ||
| import std.conv; | ||
| return text(names, ": ", a); // see testargnames for a better log function |
There was a problem hiding this comment.
Use four spaces for indentation + don't reference to DMD's testsuite in the changelog.
|
ping @timotheecour |
|
I don't have time to work on this at the moment but anyone is welcome to reopen a PR based on this;
|
Stringify caller's function argument, analog to C's
#stringification macro (except it's type safe).A main use case is to allow finally writing nice logging and debug tools.
Purely library based alternatives all have drawbacks, see #7811 (comment) for example.
It also works with UFCS, special tokens FILE etc, see examples in
test/runnable/testargnames.dEg:
EDIT
__ARG_STRING__!ato__traits(getCallerSource, a)after feedback from reviewers [2]NOTES
this is a new implementation that avoids some of the issues in [WIP] function argument stringification: __ARG_STRING__ (replaced by 7821) #7811 ; in this new implementation, parsed D source file contents are kept in memory until after end of semantic analysis [1]
currently only allows binding to a function parameter (variadic or not); other cases such as template parameters could be handled in future PR without breaking backward compatibility
i’ve dfmt-ted
test/runnable/testargnames.dbut not the rest (option to format only diff-ed lines (like git clang-format) dlang-community/dfmt#159 would help…) ; please ignore whitespace issues for now[1] I've measured that impact on memory is minimal [3] Optimizations are possible if needed, if we find use cases where the memory increase is more significant. Use new hidden flag
-disposesrcto reproduce prexisting behavior of disposing of file contents right after parsing (getSourcewill in this case resort to usingexpr.toCharswhich was similar to the previous implementation, cf #7811); you can measure memory with and without-disposesrc[2] pros/cons of
__traits(getCallerSource, a)vs__ARG_STRING__:__traitinside a function declarationpros/cons of
__ARG_STRING__:__ARG_STRING__NOTE: using getCallerSource instead of getSource to avoid conflating with a future potential
getSource[3] detailed reproducible memory tests
time is always very similar (with +/- 1 second)
Maximum resident set size varies quite a bit between runs (high variance) but the extra memory involved (at most 10M from
du -sh phobos/std) is just10M/7711924k = 1/771of the total memory usage, so negligible.On an extreme, unrealistic example that imports all of phobos and does 0 work (ie no unittests or main code), the total memory usage will be less so the fraction from keeping file contents in memory relative to total memory consumption will be higher, but still acceptable:
this 2nd testing scenario has very low variance, and we get:
(663340-655432)/655432.=1%which is acceptable (and again, this is the unfavorable case as explained)