TODO: some form of introductory paragraph about what goes under this
TODO: big picture stuff
There are two sets of choices available to the D community for this DIP. The first is the reordering of arguments with a choice between full, partial or no reordering. Partial is the default of the three and provides a transitional path to full reordering.
The second set of choices is for parameter syntax. A choice of either an attribute or a syntax based upon angle brackets. Both have benefits but because of some potential implementation constraints the attribute is the default.
To resolve and verify named arguments to named parameters the resolution algorithms are modified by appending some new logic on to the existing algorithms. The logic is the same for both functions and templates but requires hooking into the existing logic of parameter to argument confirmation.
Named parameters may have a default value associated with it. If it is not provided at the declaration site it must be provided in the arguments list. The determinance of the validity of a template initiation or function call for default values is done by the respective resolution algorithm addition listed in the Resolution section.
Named arguments specification is the same for both functions and templates.
It takes the form of Identifier : Argument
where Argument
is the respective rule.
See Argument Syntax section for further information.
TODO: link^
A named parameter may appear in the parameter list after a variadic parameter. A named argument terminates arguments passed to variadic parameter. I.e.
void func(int[] args..., @named bool flag);
func(1, 2, flag: false); // ok
func(1, 2, flag: true, 3); // error: unexpected token '3' expecting ')'
This also applied to named template parameters. I.e.
template Foo(T, U... @named V) {}
Foo!(int, float, string, V: bool); // ok
Foo!(int, float, V: bool, string); // error: unexpected token 'string' expecting ')'
Overload resolution for symbols (for functions and templates declarations) is done without named parameters. They are ignored. Ignoring named parameters in both cases means that the relevent algorithms and user code does not need to change to accomedate named parameters but they must be modified to filter them out. Giving the same behavior as currently. A side effect of this is that name mangling does not need to change to accomedate these new parameters.
An example of code that will not compiled is the following snippet:
void foo(int a) {
}
void foo(int a, @named int b) {
}
struct Bar(T) {
}
struct Bar(T, @named int Flag) {
}
Both foo
and Bar
examples are equal in showing that overload resolution does not take into account named parameters and ignroes them.
The respective usage (which won't compile as well):
foo(1); // error: matches both declarations
foo(1, b: 2); // error: matches both declarations
alias Bar1 = Bar!int; // error: matches both declarations
alias Bar2 = Bar!(int, Flag: 0); // error: matches both declarations
Templated declarations (struct, class, union, template block, mixin template) that have named parameters expose the named parameter as a member of the type.
struct MyType(@named SomeType) {
}
static assert(is(MyType!(SomeType: int) == SomeType));
The exposure of named parameters as a member of a type replaces existing code that have previously been done with an alias and a different template parameter name. The behavior with eponymous templates is that a named parameter must not match the name of the declaration. The below code will not compile and is the basis for this decision:
template Foo(Foo) {
}
pragma(msg, Foo!int.Foo);
Manifest enum's and templated alias's are treated as if they were eponymous templates and do expose named parameters as members.
It is an error to name symbols + variables the same name as a named parameter. I.e.
A template block:
template Foo(@named Bar) {
struct Bar {} // error: named template parameter collides with declaration
}
A function body:
void myFunction(@named Input)(@named Input value) {
int value; // error: named function parameter collides with declaration
alias Input = Something; // error: named template parameter collides with declaration
}
Currently the D programming language does not have the facilities to inspect template declarations for their parameters.
If you know the parameter count and if it is a value or a type, you can access to it via the is
expression.
In examples this variant of the is
expression is typically used inside of static if conditions. Where the body is valid only if the initialized template does have a given set of arguments as provided to the is
expression.
To prevent the complication of the is
expression for template instances to get access to named arguments the usage of a new trait getNamedParameters
is recommended (see Supporting Syntax for further details). To get access to the argument use the name and the getMember
trait.
TODO: link^
Function parameters, unlike template parameters are inspectable. It is possible to get storage classes, types and names of them.
This uses a second variant of the is
expression with the __parameters
specialization. However it does not return a tuple that can be modified to accomedate more information. Further a new trait isNamedParameter
is added as a shortcut to detect if a given parameter is a named parameter.
To resolve a named argument(s) against named parameters the same addition is applied to both functions and template initiations. They differ on how to determine if an argument matches the parameter but not how to determine which parameter goes with which argument.
The below process is appended to each resolution algorithm as appropriete.
- For each named argument:
- If the named argument name has already been processed in this argument list, error.
- If named arguments name is found in the named parameter list (sublist of all parameters):
- Positional argument checks go here (see reordering).
- Confirm that the argument can be passed to parameter, if not error.
- Else:
- Error.
- For each named parameter:
- If it does not have a default value or a corresponding named argument, error.
The previous resolution(s) algorithms remain unchanged, but may require refactoring to only account for unnamed arguments.
One new syntax is added to match a named argument to a named parameter.
It is split into two to cover templates and function calls.
The syntax is based upon DIP88's, using the form of Identifier : Argument
. Where Argument
matches to the respective argument type and Identifier
matches the named parameter name.
The order of evaluation of named function arguments occur in order of the arguments list (full, not subset). The order of passing of named function arguments (relative to other named function arguments and to unnamed) is platform defined. No modification to name mangling is in this DIP but may be ammended if it is a platform implementation or safety issue.
Equivalent example of function and delegate usages:
void myFunction1(@named int x, @named int y) {
}
myFunction1(x: 10, y: 10);
void myFunction2(@named T)(@named T x, @named T y) {
}
myFunction1!(T: float)(x: 10, y: 10);
Example usage using a mixin template:
mixin template Foo(T, @named ErrorType:Throwable) {
}
mixin Foo!(bool, ErrorType: Error);
The grammar that is being added:
TemplateArgument:
+ NamedTemplateArgumentList
+ NamedTemplateArgumentList:
+ NamedTemplateArgument
+ NamedTemplateArgument ,
+ NamedTemplateArgument , NamedTemplateArgumentList
+ NamedTemplateArgument:
+ Identifier : TemplateArgument
+ NamedArgument:
+ Identifier : ConditionalExpression
Two new traits are added to support type inspection. The first addition is isNamedParameter
which handles checking against a function, delegate or a function pointer if a given parameter index is a named parameter with respect to the is
expression __parameters
specialization. The second addition is getNamedParameters
which returns a list of identifiers for a given declaration, type, function, delegate or a function pointer which are named parameters.
Example usage of isNamedParameter
trait with respect to a function.
void myFunction(@named int a) {
}
static if (is(typeof(func) Params == __parameters)) {
static foreach(i, Param; Params) {
pragma(msg, Param); // int
pragma(msg, __traits(identifier, Params[i .. i+1])); // a
pragma(msg, __traits(isNamedParameter, typeof(func), i)); // true
}
}
The trait isNamedParameter
has the same signature as getParameterStorageClasses
. The description from the specification is:
Takes two arguments.
The first must either be a function symbol, or a type that is a function, delegate or a function pointer.
The second is an integer identifying which parameter, where the first parameter is 0.
It returns a tuple of strings representing the storage classes of that parameter.
The difference between the traits isNamedParameter
and getParameterStorageClasses
is that isNamedParameter
returns a boolean on if the parameter is a named parameter or not.
Example usage of getNamedParameters
trait with respect to a function pointer and struct declaration.
struct MyType(@named Info) {
}
pragma(msg, __traits(getNamedParameters, MyType)); // tuple("Info");
pragma(msg, __traits(getNamedParameters, MyType!(Info: int))); // tuple("Info");
alias MyFunction = void function(@named bool myFlag);
pragma(msg, __traits(getNamedParameters, MyFunction)); // tuple("myFlag");
The additional grammar:
TraitsKeyword:
+ isNamedParameter
+ getNamedParameters
a template parameter is a part of a type. when you construct a type, you don't have it already. so, you speak about template parameter as part of declaration