www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Extracting user defined attributes on function parameters

reply Jean-Louis Leroy <jl leroy.nyc> writes:
I can see them:

     import std.traits;

     struct foo;
     struct bar;

     void f( foo int,  foo  bar  ("baz") real);

     pragma(msg, Parameters!f);
     // ( (foo) int,  (tuple(tuple(foo), tuple(bar)), 
tuple("baz")) real)

...but I cannot find how to get hold of them:

     pragma(msg, (Mystery!f)[0]); // foo
     pragma(msg, __traits(mystery, f)[0]); // foo

And besides the structure looks weird for the second argument. 
It's as if the UDAs were applied to one another, from left to 
right.

I did search the documentation and google for 1/2 hour.
Apr 14 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 14 April 2020 at 21:35:12 UTC, Jean-Louis Leroy wrote:
 I can see them:
There's some weird tricks to it. Check out my old blog sidebar about it here: http://dpldocs.info/this-week-in-d/Blog.Posted_2019_02_11.html#how-to-get-uda-on-a-function-param
Apr 14 2020
parent reply Jean-Louis Leroy <jl leroy.nyc> writes:
On Tuesday, 14 April 2020 at 21:44:51 UTC, Adam D. Ruppe wrote:
 On Tuesday, 14 April 2020 at 21:35:12 UTC, Jean-Louis Leroy 
 wrote:
 I can see them:
There's some weird tricks to it. Check out my old blog sidebar about it here: http://dpldocs.info/this-week-in-d/Blog.Posted_2019_02_11.html#how-to-get-uda-on-a-function-param
O.....kay. It looks like `is` is D's Swiss army chainsaw. Yeah I never read the `is` doc properly. Now I am beginning to understand how `std.traits.Parameters` works. Thanks!
Apr 14 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 14 April 2020 at 21:54:14 UTC, Jean-Louis Leroy wrote:
 O.....kay. It looks like `is` is D's Swiss army chainsaw.
Aye, is and __traits are the two built-in compile-time reflection avenues. The phobos std.traits things (for the most part, look at the source for the default parameter values thing and lol you'll see some of them get quite involved) wrap them. is() is a bit weird, but I described it in my "D Cookbook" to some success... and writing that description helped me make sense of it. The docs list like seven forms of it, but they are mostly just one big thing with a lot of optional pieces you can leave out, coupled with a little bit of magic when you use certain keywords, like __parameters. https://dlang.org/spec/expression.html#is_expression I like to think of it as a kind of magic variable declaration is(SomeExistingType yourOptionalIdenfier == some_optional_pattern) (or you can use : instead of == for a looser match. == is equality, and : indicates implicit conversion, like how class Foo : Interface can implicitly convert to Interface, so `is(Foo : Interface)` passes. If you leave off both the ident and the pattern, it just check is the type is a type: static if(is(Object)) // passed because Object is a type static if(is(10)) // fails because 10 isn't a type, it is a value static if(is(adasdsa)) // fails because undefined identifier If you provide the optional identifier, it aliases the match inline for you. I think of the syntax as being like a variable declaration. So just like int a; // defines a new variable with the name "a" static if(is(Object a)) // defines a new alias with the name "a" being the same as Object Then you add on the pattern. These are generally written like the variable declaration too, but you can define placeholders. Simple example: static if(is(typeof(a) == int)) more complex example: static if(is(typeof(a) == T[], T)) In that one, the pattern is "T[]", looking like a type, but then since I specified ", T" afterward, it means T is a placeholder. So that would match if typeof(a) is some kind of array, and then T gets the element type. int[] a; static if(is(typeof(a) == T[], T)) // passes because a is an array, T == int Those can get pretty complex because you're allowed to add as many placeholders as you need for an arbitrarily complex pattern, though I prefer to keep it kinda simple and use two levels of static if to cover more complicated things. Then the last magic is the pattern on the right-hand side can be various keywords. static if(is(Object == class)) passes if Object is a class. You can also do that with struct, interface, union, const, immutable, and shared. But if you look at the docs, you see there are a few other magic keywords too, like `enum`, return, function, delegate, super, and __parameters. They still match what you expect basically - if it is a function pointer, or an enum, etc., but the real magic is they change what the alias you can define in the is() expression refers to. So then it isn't just a convenience alias for the condition, it actually becomes an aspect of that type, like the return type, or the params, or the parent class. So yes a swiss army chainsaw :P but once you know its few tricks and patterns it isn't as hard as the docs make it look.
Apr 14 2020
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Apr 15, 2020 at 12:01:51AM +0000, Adam D. Ruppe via Digitalmars-d-learn
wrote:
[...]
 is() is a bit weird, but I described it in my "D Cookbook" to some
 success... and writing that description helped me make sense of it.
 The docs list like seven forms of it, but they are mostly just one big
 thing with a lot of optional pieces you can leave out, coupled with a
 little bit of magic when you use certain keywords, like __parameters.
is(T params == __parameters) is one of the most evil arcane black magic corners of D, as I wrote here: https://forum.dlang.org/post/mailman.1197.1379014886.1719.digitalmars-d-learn puremagic.com It feels like it was shoehorned into is() because that was the most convenient place at the time, but it's really a strange arcane beast that has its own quirky behaviours that are different from anything else I've seen in D. T -- The irony is that Bill Gates claims to be making a stable operating system and Linus Torvalds claims to be trying to take over the world. -- Anonymous
Apr 14 2020
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, Friday, 17 Apr 2020 17:45:47 UTC, H. S. Teoh wrote:

 I wonder if the ultimate cause of the above case is ultimately 
 caused by
 the change to import semantics that hid private symbols from 
 outside the
 module. Perhaps something, somewhere, is triggering an illegal 
 access of
 a private symbol, which percolates up the call/instantiation 
 stack and
 causing what appears to be a strange compiler discrepancy.
Not unlikely. Importing the module defining S in the module defining ParameterDefaults does indeed make things compile. Hiding S by making it private makes the error return. (for whatever reason your message isn't visible in the web interface) -- Simen
Apr 18 2020
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 18 April 2020 at 09:19:48 UTC, Simen Kjærås wrote:
 On Wednesday, Friday, 17 Apr 2020 17:45:47 UTC, H. S. Teoh 
 wrote:

 I wonder if the ultimate cause of the above case is ultimately 
 caused by
 the change to import semantics that hid private symbols from 
 outside the
 module. Perhaps something, somewhere, is triggering an illegal 
 access of
 a private symbol, which percolates up the call/instantiation 
 stack and
 causing what appears to be a strange compiler discrepancy.
Not unlikely. Importing the module defining S in the module defining ParameterDefaults does indeed make things compile. Hiding S by making it private makes the error return.
It's not about private. Or even if it is, there's other issues. Proof: struct A { struct S {} void f( S int = 3); pragma(msg, Issue20744!f); } template Issue20744(func...) { static if (is(typeof(func[0]) PT == __parameters)) { alias Issue20744 = (PT args) {}; } } -- Simen
Apr 18 2020
prev sibling next sibling parent Jean-Louis Leroy <jl leroy.nyc> writes:
Thanks to both of you!

As part of implementing full support for attributes in 
openmethods, I am developing a reflection library. That helped a 
lot.

 is() is a bit weird, but I described it in my "D Cookbook" to 
 some success...
I am going to order it...even though it is not available on Kindle ;-)
Apr 15 2020
prev sibling parent reply Jean-Louis Leroy <jl leroy.nyc> writes:
Alas the presence of parameter UDAs breaks 
std.traits.ParameterDefaults:

import std.traits;

struct attr;
void f( attr int);

pragma(msg, ParameterDefaults!f.stringof);

Error:

dmd -c bug.d
bug.d(4): Error: undefined identifier `attr`, did you mean 
variable `ptr`?
/home/jll/dlang/dmd-2.090.1/linux/bin64/../../src/phobos/std/traits.d(1526):
Error: template instance `std.traits.ParameterDefaults!(f).Get!0LU` error
instantiating
/home/jll/dlang/dmd-2.090.1/linux/bin64/../../src/phobos/std/traits.d(1529):   
    instantiated from here: `Impl!0LU`
bug.d(6):        instantiated from here: `ParameterDefaults!(f)`
bug.d(6):        while evaluating `pragma(msg, 
ParameterDefaults!(f).stringof)`

I filed a bug report (20744). And examined the code of 
ParameterDefaults. I think I understand how it works, for the 
most part, but I haven't been able to find a fix yet.

I'd like to understand why taking a slice of __parameters vs 
fetching the first element matters. What is the (meta?) type of 
__parameters[0..1]? I think I'd need to make a copy of it, minus 
the UDAs tucked at the beginning. But I haven't found a way of 
splitting it into smaller components. I tried using indexation 
and variadic template parameters, but it always collapses into a 
string. Makes me think of wave functions in quantum mechanics ;-)
Apr 17 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 17 April 2020 at 16:40:15 UTC, Jean-Louis Leroy wrote:
 Alas the presence of parameter UDAs breaks 
 std.traits.ParameterDefaults:

 import std.traits;

 struct attr;
 void f( attr int);
This part seems fine...
 pragma(msg, ParameterDefaults!f.stringof);
It is this, specifically, that causes the problem. Replace it with: void main() { import std.stdio; writeln(ParameterDefaults!f.stringof); } and it is fine. So pragma(msg) is doing something really weird, the bug doesn't appear to be in Phobos per se, I think it is the compiler doing the wrong thing, it seems to me it works inside a function scope but not at module scope......
 I'd like to understand why taking a slice of __parameters vs 
 fetching the first element matters. What is the (meta?) type of 
 __parameters[0..1]?
The first element alone becomes a type. The slice maintains the magic data inside the compiler; it contains stuff the rest of the language cannot express by itself except in parameter lists. It is weird.
 collapses into a string. Makes me think of wave functions in 
 quantum mechanics ;-)
well it is dependent on when the compiler observes it sooooo lol
Apr 17 2020
next sibling parent reply Jean-Louis Leroy <jl leroy.nyc> writes:
On Friday, 17 April 2020 at 16:54:42 UTC, Adam D. Ruppe wrote:
 void main() {
         import std.stdio;
         writeln(ParameterDefaults!f.stringof);
 }

 and it is fine.
Well, can't do. I need this purely at compile time, and cross-module. That's for supporting UDAs and default parameter values in openmethods. If you want a peek at what I am up to, see here: https://github.com/jll63/openmethods.d/blob/bolts-reflection/source/bolts/reflection/metafunction.d
Apr 17 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 17 April 2020 at 17:31:32 UTC, Jean-Louis Leroy wrote:
 Well, can't do. I need this purely at compile time, and 
 cross-module.
And the CTFE engine gets weird with it too.... dmd will have to fix this. But default parameters might not be possible in general at CT anyway... it is actually possible to define it to a variable. Observe: int a; void f(int arg = a) { // default arg is to use the global... import std.stdio; writeln(arg); } void main() { f(); // 0 this time a = 4; // and it can be changed at runtime! f(); // will now say 4 }
Apr 17 2020
parent reply Jean-Louis Leroy <jl leroy.nyc> writes:
On Friday, 17 April 2020 at 17:48:06 UTC, Adam D. Ruppe wrote:
 On Friday, 17 April 2020 at 17:31:32 UTC, Jean-Louis Leroy 
 wrote:
 Well, can't do. I need this purely at compile time, and 
 cross-module.
And the CTFE engine gets weird with it too.... dmd will have to fix this. But default parameters might not be possible in general at CT anyway... it is actually possible to define it to a variable. Observe: int a; void f(int arg = a) { // default arg is to use the global... import std.stdio; writeln(arg); } void main() { f(); // 0 this time a = 4; // and it can be changed at runtime! f(); // will now say 4 }
Interesting example, but all hope is not lost. `a` could (should?) be passed as an alias in __parameters.
Apr 17 2020
next sibling parent Jean-Louis Leroy <jl leroy.nyc> writes:
On Friday, 17 April 2020 at 18:05:39 UTC, Jean-Louis Leroy wrote:

 Interesting example, but all hope is not lost. `a` could 
 (should?) be passed as an alias in __parameters.
Okay I take this back...
Apr 17 2020
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 17 April 2020 at 18:05:39 UTC, Jean-Louis Leroy wrote:
 Interesting example, but all hope is not lost. `a` could 
 (should?) be passed as an alias in __parameters.
Well, __parameters itself actually kinda works. The compiler knows it is an expression and can stringify it or evaluate it on demand for you... but how to express that in D code is pretty weird and idk how to library that. Using in-site for various things can be done but putting it in a lib is idk.
Apr 17 2020
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Friday, 17 April 2020 at 16:54:42 UTC, Adam D. Ruppe wrote:
 This part seems fine...

 pragma(msg, ParameterDefaults!f.stringof);
It is this, specifically, that causes the problem. Replace it with: void main() { import std.stdio; writeln(ParameterDefaults!f.stringof); } and it is fine. So pragma(msg) is doing something really weird, the bug doesn't appear to be in Phobos per se, I think it is the compiler doing the wrong thing, it seems to me it works inside a function scope but not at module scope......
It's even more fascinating - the issue doesn't occur if ParameterDefaults is defined in the same module that it's used in, and it works if there's a type with the same name as the UDA. Reducing the code as much as I can, I get this: struct S {} void f( S int = 3); pragma(msg, ParameterDefaults!f.stringof); template ParameterDefaults(func...) { import std.traits : FunctionTypeOf; static if (is(FunctionTypeOf!(func[0]) PT == __parameters)) { enum ParameterDefaults = (PT[0..1] args) trusted { return *&(args[0]); }(); } } The above code works, and prints "3". If you move ParameterDefaults to a different module, something like this: ----- import bar; struct S {} void f( S int = 3); pragma(msg, ParameterDefaults!f.stringof); ----- module bar; template ParameterDefaults(func...) { static if (is(typeof(func[0]) PT == __parameters)) { enum ParameterDefaults = (PT[0..1] args) trusted { return *&(args[0]); }(); } } ----- Then you get an error message about 'undefined identifier S'. Add some kind of S to bar, and you get an error message about S not being readable at compile-time or things just work if it is readable. It seems the UDA is being looked up in the wrong context. -- Simen
Apr 17 2020
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Apr 17, 2020 at 05:33:23PM +0000, Simen Kjærås via Digitalmars-d-learn
wrote:
 On Friday, 17 April 2020 at 16:54:42 UTC, Adam D. Ruppe wrote:
[...]
 So pragma(msg) is doing something really weird, the bug doesn't
 appear to be in Phobos per se, I think it is the compiler doing the
 wrong thing, it seems to me it works inside a function scope but not
 at module scope......
It's even more fascinating - the issue doesn't occur if ParameterDefaults is defined in the same module that it's used in, and it works if there's a type with the same name as the UDA. Reducing the code as much as I can, I get this:
[...]
 The above code works, and prints "3". If you move ParameterDefaults to
 a different module, something like this:
[...]
 Then you get an error message about 'undefined identifier S'. Add some
 kind of S to bar, and you get an error message about S not being
 readable at compile-time or things just work if it is readable. It
 seems the UDA is being looked up in the wrong context.
This is reminiscient of a bug I found recently, the ultimate cause of which is accessing a private symbol across modules, which is verboten, but it manifested itself in a way that appeared completely unrelated -- it turned an attribute-inferred function into system where one expected safe, which percolated up the call stack and ended up as a template mismatch error, which then shunted the template resolution into another overload which finally generated a totally unrelated compile error (very unhelpful!). I wonder if the ultimate cause of the above case is ultimately caused by the change to import semantics that hid private symbols from outside the module. Perhaps something, somewhere, is triggering an illegal access of a private symbol, which percolates up the call/instantiation stack and causing what appears to be a strange compiler discrepancy. Or perhaps it's a compiler bug. :-D T -- It is of the new things that men tire --- of fashions and proposals and improvements and change. It is the old things that startle and intoxicate. It is the old things that are young. -- G.K. Chesterton
Apr 17 2020