Jump to page: 1 2 3
Thread overview
string file = __FILE__ considered harmful (and solution)
May 30, 2018
FeepingCreature
May 30, 2018
rikki cattermole
May 30, 2018
FeepingCreature
SOLUTION DOES NOT WORK
May 30, 2018
FeepingCreature
Proposal?
May 30, 2018
FeepingCreature
May 30, 2018
H. S. Teoh
May 30, 2018
FeepingCreature
May 30, 2018
Daniel N
May 30, 2018
Daniel N
May 30, 2018
H. S. Teoh
May 30, 2018
bauss
May 30, 2018
FeepingCreature
May 30, 2018
John Colvin
Jun 01, 2018
Walter Bright
Jun 01, 2018
Mike Franklin
Jun 01, 2018
Walter Bright
May 31, 2018
bauss
Jun 01, 2018
Walter Bright
May 30, 2018
There's a very common idiom where in order to report line numbers of an error or a log line at the callsite of a function, you pass __FILE__ and __LINE__ as default parameters:

void foo(string file = __FILE__, size_t line = __LINE__);

What's wrong with this?

Say you add a string parameter, such as

void foo(string msg, string file = __FILE__, size_t line = __LINE__);

foo("Hello World");

Now when you accidentally grab an old version of the library, your new code will still run, but it will believe that it's being called from file "Hello World", line 15. Not good.

Luckily there's a fix. Just stick this in some common header file in your project:

struct CallerInfo
{
  string file;
  size_t line;
}

void foo(string msg, CallerInfo caller = CallerInfo(__FILE__, __LINE__));

Now you cannot accidentally invoke foo with a string, or in fact any type except another instance of CallerInfo.

This is such a basic type that it really belongs in phobos, arguably object.d. At which point we can shorten this to CallerInfo caller = __CALLER__, and be forward compatible for additional information about the callsite, such as, say, attributes of the calling function.
May 30, 2018
On 30/05/2018 8:27 PM, FeepingCreature wrote:
> There's a very common idiom where in order to report line numbers of an error or a log line at the callsite of a function, you pass __FILE__ and __LINE__ as default parameters:
> 
> void foo(string file = __FILE__, size_t line = __LINE__);
> 
> What's wrong with this?
> 
> Say you add a string parameter, such as
> 
> void foo(string msg, string file = __FILE__, size_t line = __LINE__);
> 
> foo("Hello World");
> 
> Now when you accidentally grab an old version of the library, your new code will still run, but it will believe that it's being called from file "Hello World", line 15. Not good.
> 
> Luckily there's a fix. Just stick this in some common header file in your project:
> 
> struct CallerInfo
> {
>    string file;
>    size_t line;
> }
> 
> void foo(string msg, CallerInfo caller = CallerInfo(__FILE__, __LINE__));
> 
> Now you cannot accidentally invoke foo with a string, or in fact any type except another instance of CallerInfo.
> 
> This is such a basic type that it really belongs in phobos, arguably object.d. At which point we can shorten this to CallerInfo caller = __CALLER__, and be forward compatible for additional information about the callsite, such as, say, attributes of the calling function.

ooo I have another solution.

Use a named argument[0]!

[0] https://github.com/rikkimax/DIPs/blob/named_args/DIPs/DIP1xxx-RC.md
May 30, 2018
Shit it doesn't work, I only checked if it compiled; it gives the wrong file/line number.

NEVERMIND ALL
May 30, 2018
Updated subject to be visible at a glance.

Note that a compiler-based solution via __CALLER__ would still work.
May 30, 2018
On Wednesday, 30 May 2018 at 08:27:16 UTC, FeepingCreature wrote:
> struct CallerInfo
> {
>   string file;
>   size_t line;
> }

Let me try to flesh this out.

void foo(CallContext caller = __CALL_CONTEXT__)
{
}

// in druntime object.d
struct CallContext
{
  public size_t line; // expected to change for every call
  private const FunctionContext* functionContext; // expected to be constant for many calls
  alias functionContext this;
}

/** a variable with this type is lazily allocated once per function */
struct FunctionContext
{
  string file, fileFullPath, module, function, prettyFunction;
}

I've looked at DMDFE and I don't think I know enough to do this, annoyingly.
May 30, 2018
What about this?

------
struct EndOfArgs { }
EndOfArgs eoa;

void func(string s, EndOfArgs _ = eoa,
	string file = __FILE__, size_t line = __LINE__)
{
	import std.stdio;
	writefln("%s:%d: msg=%s", file, line, s);
}

void main() {
	func("hello");
	func("there");
}
------


Basically, use a dummy empty struct to differentiate between real arguments and context info.


T

-- 
Don't drink and derive. Alcohol and algebra don't mix.
May 30, 2018
On Wednesday, 30 May 2018 at 08:27:16 UTC, FeepingCreature wrote:
> There's a very common idiom where in order to report line numbers of an error or a log line at the callsite of a function, you pass __FILE__ and __LINE__ as default parameters:
>
> [...]

void foo(string file = __FILE__, size_t line = __LINE__)(string msg);

Wouldn't this solve it?

May 30, 2018
On Wednesday, 30 May 2018 at 11:59:05 UTC, bauss wrote:
> On Wednesday, 30 May 2018 at 08:27:16 UTC, FeepingCreature wrote:
>> There's a very common idiom where in order to report line numbers of an error or a log line at the callsite of a function, you pass __FILE__ and __LINE__ as default parameters:
>>
>> [...]
>
> void foo(string file = __FILE__, size_t line = __LINE__)(string msg);
>
> Wouldn't this solve it?

No, because a) then you're completely pointlessly making a foo for every line it's called in, b) you're not future compatible with, say, call column, and c) you get exactly the same problem with template value parameters, ie. foo!"some ct argument".
May 30, 2018
On Wednesday, 30 May 2018 at 10:05:42 UTC, H. S. Teoh wrote:
> What about this?
>
> ------
> struct EndOfArgs { }
> EndOfArgs eoa;
>
> void func(string s, EndOfArgs _ = eoa,
> 	string file = __FILE__, size_t line = __LINE__)
> {
> 	import std.stdio;
> 	writefln("%s:%d: msg=%s", file, line, s);
> }
>
> void main() {
> 	func("hello");
> 	func("there");
> }
> ------
>
>
> Basically, use a dummy empty struct to differentiate between real arguments and context info.
>
>
> T

Thank you, this seems to work well!

We're using struct Fence { } Fence _ = Fence(), and it doesn't add much overhead.

Barring the proposed compiler change, this seems the cleanest fix.
May 30, 2018
On 5/30/18 4:27 AM, FeepingCreature wrote:
> There's a very common idiom where in order to report line numbers of an error or a log line at the callsite of a function, you pass __FILE__ and __LINE__ as default parameters:
> 
> void foo(string file = __FILE__, size_t line = __LINE__);
> 
> What's wrong with this?
> 
> Say you add a string parameter, such as
> 
> void foo(string msg, string file = __FILE__, size_t line = __LINE__);
> 
> foo("Hello World");
> 
> Now when you accidentally grab an old version of the library, your new code will still run, but it will believe that it's being called from file "Hello World", line 15. Not good.
> 
> Luckily there's a fix. Just stick this in some common header file in your project:
> 
> struct CallerInfo
> {
>    string file;
>    size_t line;
> }
> 
> void foo(string msg, CallerInfo caller = CallerInfo(__FILE__, __LINE__));
> 
> Now you cannot accidentally invoke foo with a string, or in fact any type except another instance of CallerInfo.

Awesome idea! Unfortunately, it doesn't work. The __FILE__ and __LINE__ there are not from the caller, but from the line that defines foo.

See here: https://run.dlang.io/is/siz9YZ

> At which point we can shorten this to CallerInfo caller = __CALLER__, and be forward compatible for additional information about the callsite, such as, say, attributes of the calling function.

Hm.. I don't like this too much. Adding more magic to the compiler seems unnecessary.

But if we fixed the behavior that causes your idea not to work, then we could probably easily define a function like so:

CallerInfo __CALLER__(string file = __FILE__, size_t line = __LINE__)
{
    return CallerInfo(file, line);
}

-Steve
« First   ‹ Prev
1 2 3