PropertyType

PropertyType evaluates to the type of the symbol as an expression (i.e. the type of the expression when the symbol is used in an expression by itself), and SymbolType evaluates to the type of the given symbol as a symbol (i.e. the type of the symbol itself).

Unlike with $(K_TYPEOF), $(K_PROPERTY) has no effect on the result of either PropertyType or SymbolType.

TLDR, PropertyType should be used when code is going to use a symbol as if it were a getter property (without caring whether the symbol is a variable or a function), and the code needs to know the type of that property and not the type of the symbol itself (since with functions, they're not the same thing). So, it's treating the symbol as an expression on its own and giving the type of that expression rather than giving the type of the symbol itself. And it's named PropertyType rather than something like ExpressionType, because it's used in situations where the code is treating the symbol as a getter property, and it does not operate on expressions in general. SymbolType should then be used in situations where the code needs to know the type of the symbol itself.

As for why PropertyType and SymbolType are necessary, and $(K_TYPEOF) is not sufficient, $(K_TYPEOF) gives both the types of symbols and the types of expressions. When it's given something that's clearly an expression - e.g. typeof(foo + bar) or typeof(foo()), then the result is the type of that expression. However, when $(K_TYPEOF) is given a symbol, what $(K_TYPEOF) does depends on the symbol, which can make using it correctly in generic code difficult.

For symbols that don't have types, naturally, $(K_TYPEOF) doesn't even compile (e.g. typeof(int) won't compile, because in D, types don't themselves have types), so the question is what happens with $(K_TYPEOF) and symbols which do have types.

For symbols which have types and are not functions, what $(K_TYPEOF) does is straightforward, because there is no difference between the type of the symbol and the type of the expression - e.g. if foo is a variable of type $(K_INT), then typeof(foo) would be $(K_INT), because the variable itself is of type $(K_INT), and if the variable is used as an expression (e.g. when it's returned from a function or passed to a function), then that expression is also of type $(K_INT).

However, for functions (particularly functions which don't require arguments), things aren't as straightforward. Should typeof(foo) give the type of the function itself or the type of the expression? If parens always had to be used when calling a function, then typeof(foo) would clearly be the type of the function itself, whereas typeof(foo()) would be the type of the expression (which would be the return type of the function so long as foo could be called with no arguments, and it wouldn't compile otherwise, because the expression would be invalid). However, because parens are optional when calling a function that has no arguments, typeof(foo) is ambiguous. There's no way to know which the programmer actually intended, but the compiler has to either make it an error or choose between giving the type of the symbol or the type of the expression.

So, the issue here is functions which can be treated as getter properties, because they can be called without parens and thus syntactically look exactly like a variable when they're used. Any function which requires arguments (including when a function is used as a setter property) does not have this problem, because it isn't a valid expression when used on its own (it needs to be assigned to or called with parens in order to be a valid expression).

What the compiler currently does when it encounters this ambiguity depends on the $(K_PROPERTY) attribute. If the function does not have the $(K_PROPERTY) attribute, then typeof(foo) will give the type of the symbol - e.g. int() if the signature for foo were int foo(). However, if the function does have the $(K_PROPERTY) attribute, then typeof(foo) will give the type of the expression. So, if foo were @property int foo(), then typeof(foo) would give $(K_INT) rather than int(). The idea behind this is that $(K_PROPERTY) functions are supposed to act like variables, and using $(K_TYPEOF) on a variable of type $(K_INT) would give $(K_INT), not int(). So, with this behavior of $(K_TYPEOF), a $(K_PROPERTY) function is closer to being a drop-in replacement for a variable.

The problem with this though is two-fold. One, it means that $(K_TYPEOF) cannot be relied on to give the type of the symbol when given a symbol on its own, forcing code that needs the actual type of the symbol to work around $(K_PROPERTY). And two, because parens are optional on functions which can be called without arguments, whether the function is marked with $(K_PROPERTY) or not is irrevelant to whether the symbol is going to be used as if it were a variable, and so it's irrelevant to code that's trying to get the type of the expression when the symbol is used like a getter property. If optional parens were removed from the language (as was originally the intent when $(K_PROPERTY) was introduced), then that would fix the second problem, but it would still leave the first problem.

So, $(K_PROPERTY) is solving the problem in the wrong place. It's the code doing the type introspection which needs to decide whether to get the type of a symbol as if it were a getter property or whether to get the type of the symbol itself. It's the type introspection code which knows which is relevant for what it's doing, and a function could be used both in code which needs to treat it as a getter property and in code which needs to get the type of the symbol itself (e.g. because it needs to get the attributes on the function).

All of this means that $(K_TYPEOF) by itself is unreliable when used on a symbol. In practice, the programmer needs to indicate whether they want the type of the symbol itself or the type of the symbol as a getter property in an expression, and leaving it up to $(K_TYPEOF) is simply too error-prone. So, ideally, $(K_TYPEOF) would be split up into two separate constructs, but that would involve adding more keywords (and break a lot of existing code if $(K_TYPEOF) were actually removed).

So, phobos.sys.traits provides SymbolType and PropertyType. They're both traits that take a symbol and give the type for that symbol. However, SymbolType gives the type of the symbol itself, whereas PropertyType gives the type of the symbol as if it were used as a getter property in an expression. Neither is affected by whether the symbol is marked with $(K_PROPERTY). So, code that needs to get information about the symbol itself should use SymbolType rather than $(K_TYPEOF) or PropertyType, whereas code that needs to get the type of the symbol as a getter property within an expression should use PropertyType.

The use of $(K_TYPEOF) should then be restricted to situations where code is getting the type of an expression which isn't a symbol (or code where it's already known that the symbol isn't a function). Also, since template alias parameters only accept symbols, any expressions which aren't symbols won't compile with SymbolType or PropertyType anyway.

SymbolType and PropertyType must be given a symbol which has a type (so, not something like a type or an uninstantiated template). Symbols which don't have types will fail the template constraint.

For both SymbolType and PropertyType, if they are given a symbol which has a type, and that symbol is not a function, the result will be the same as $(K_TYPEOF) (since in such cases, the type of the symbol and the type of the expression are the same). The difference comes in with functions.

When SymbolType is given any symbol which is a function, the result will be the type of the function itself regardless of whether the function is marked with $(K_PROPERTY). This makes it so that in all situations where the type of the symbol is needed, SymbolType can be used to get the type of the symbol without having to worry about whether it's a function marked with $(K_PROPERTY).

When PropertyType is given any function which can be used as a getter property, the result will be the type of the symbol as an expression - i.e. the return type of the function (in effect, this means that it treats all functions as if they were marked with $(K_PROPERTY)). Whether the function is actually marked with $(K_PROPERTY) or not is irrelevant.

If PropertyType is given any function which which cannot be used as a getter property (i.e. it requires arguments or returns $(K_VOID)), the template constraint will reject it, and PropertyType will fail to compile. This is equivalent to what $(K_TYPEOF) does when it's given a $(K_PROPERTY) function which is a setter, since it's not a valid expression on its own.

So, for PropertyType!foo, if foo is a function, the result is equivalent to typeof(foo()) (and for non-functions, it's equivalent to typeof(foo)).

To summarize, SymbolType should be used when code needs to get the type of the symbol itself; PropertyType should be used when code needs to get the type of the symbol when it's used in an expression as a getter property (generally because the code doesn't care whether the symbol is a variable or a function); and $(K_TYPEOF) should be used when getting the type of an expression which is not a symbol.

template PropertyType (
alias sym
) if (
is(typeof(sym)) &&
(
!is(typeof(sym) == return) ||
(
is(typeof(sym())) &&
!is(typeof(sym()) == void)
)
)
) {}

Examples

1 int i;
2 static assert( is(SymbolType!i == int));
3 static assert( is(PropertyType!i == int));
4 static assert( is(typeof(i) == int));
5 
6 string str;
7 static assert( is(SymbolType!str == string));
8 static assert( is(PropertyType!str == string));
9 static assert( is(typeof(str) == string));
10 
11 // ToFunctionType is used here to get around the fact that we don't have a
12 // way to write out function types in is expressions (whereas we can write
13 // out the type of a function pointer), which is a consequence of not being
14 // able to declare variables with a function type (as opposed to a function
15 // pointer type). That being said, is expressions are pretty much the only
16 // place where writing out a function type would make sense.
17 
18 // The function type has more attributes than the function declaration,
19 // because the attributes are inferred for nested functions.
20 
21 static string func() { return ""; }
22 static assert( is(SymbolType!func ==
23                   ToFunctionType!(string function()
24                                   @safe pure nothrow @nogc)));
25 static assert( is(PropertyType!func == string));
26 static assert( is(typeof(func) == SymbolType!func));
27 
28 int function() funcPtr;
29 static assert( is(SymbolType!funcPtr == int function()));
30 static assert( is(PropertyType!funcPtr == int function()));
31 static assert( is(typeof(funcPtr) == int function()));
32 
33 int delegate() del;
34 static assert( is(SymbolType!del == int delegate()));
35 static assert( is(PropertyType!del == int delegate()));
36 static assert( is(typeof(del) == int delegate()));
37 
38 @property static int prop() { return 0; }
39 static assert( is(SymbolType!prop ==
40                   ToFunctionType!(int function()
41                                   @property @safe pure nothrow @nogc)));
42 static assert( is(PropertyType!prop == int));
43 static assert( is(typeof(prop) == PropertyType!prop));
44 
45 // Functions which cannot be used as getter properties (i.e. they require
46 // arguments and/or return void) do not compile with PropertyType.
47 static int funcWithArg(int i) { return i; }
48 static assert( is(SymbolType!funcWithArg ==
49                   ToFunctionType!(int function(int)
50                                   @safe pure nothrow @nogc)));
51 static assert(!__traits(compiles, PropertyType!funcWithArg));
52 static assert( is(typeof(funcWithArg) == SymbolType!funcWithArg));
53 
54 // Setter @property functions also don't work with typeof, because typeof
55 // gets the type of the expression rather than the type of the symbol when
56 // the symbol is a function with @property, and a setter property is not a
57 // valid expression on its own.
58 @property static void prop2(int) {}
59 static assert( is(SymbolType!prop2 ==
60                   ToFunctionType!(void function(int)
61                                   @property @safe pure nothrow @nogc)));
62 static assert(!__traits(compiles, PropertyType!prop2));
63 static assert(!__traits(compiles, typeof(prop2)));
64 
65 // Expressions which aren't symbols don't work with alias parameters and
66 // thus don't work with SymbolType or PropertyType.
67 static assert(!__traits(compiles, PropertyType!(i + 42)));
68 static assert(!__traits(compiles, SymbolType!(i + 42)));
69 static assert( is(typeof(i + 42) == int));
70 
71 // typeof will work with a function that takes arguments so long as it's
72 // used in a proper expression.
73 static assert( is(typeof(funcWithArg(42)) == int));
74 static assert( is(typeof(prop2 = 42) == void));
75 static assert( is(typeof(prop2(42)) == void));

With templated types or functions, a specific instantiation should be passed to SymbolType or PropertyType, not the symbol for the template itself. If $(K_TYPEOF), SymbolType, or PropertyType is used on a template (rather than an instantiation of the template), the result will be $(K_VOID), because the template itself does not have a type.

static T func(T)() { return T.init; }

static assert(is(SymbolType!func == void));
static assert(is(PropertyType!func == void));
static assert(is(typeof(func) == void));

static assert(is(SymbolType!(func!int) ==
                 ToFunctionType!(int function()
                                 @safe pure nothrow @nogc)));
static assert(is(PropertyType!(func!int) == int));
static assert(is(typeof(func!int) == SymbolType!(func!int)));

static assert(is(SymbolType!(func!string) ==
                 ToFunctionType!(string function()
                                 @safe pure nothrow @nogc)));
static assert(is(PropertyType!(func!string) == string));
static assert(is(typeof(func!string) == SymbolType!(func!string)));

If a function is overloaded, then when using it as a symbol to pass to $(K_TYPEOF), the compiler typically selects the first overload. However, if the functions are marked with $(K_PROPERTY), and one of the overloads is a getter property, then $(K_TYPEOF) will select the getter property (or fail to compile if they're all setter properties). This is because it's getting the type of the function as an expression rather than doing introspection on the function itself.

SymbolType always gives the type of the first overload (effectively ignoring $(K_PROPERTY)), and PropertyType always gives the getter ovrerload (effectively treating all functions as if they had $(K_PROPERTY)).

If code needs to get the symbol for a specific overload, then $(DDSUBLINK spec/traits, getOverloads, __traits(getOverloads, ...) must be used.

In general, getOverloads should be used when using SymbolType, since there's no guarantee that the first one is the correct one (and often, code will need to check all of the overloads), whereas with PropertyType, it doesn't usually make sense to get specific overloads, because there can only ever be one overload which works as a getter property, and using PropertyType on the symbol for the function will give that overload if it's present, regardless of which overload is first (and it will fail to compile if there is no overload which can be called as a getter).

1 static struct S
2 {
3     string foo();
4     void foo(string);
5     bool foo(string, int);
6 
7     @property void bar(int);
8     @property int bar();
9 }
10 
11 {
12     static assert( is(SymbolType!(S.foo) ==
13                       ToFunctionType!(string function())));
14     static assert( is(PropertyType!(S.foo) == string));
15     static assert( is(typeof(S.foo) == SymbolType!(S.foo)));
16 
17     alias overloads = __traits(getOverloads, S, "foo");
18 
19     // string foo();
20     static assert( is(SymbolType!(overloads[0]) == function));
21     static assert( is(PropertyType!(overloads[0]) == string));
22     static assert( is(typeof(overloads[0]) == function));
23 
24     static assert( is(SymbolType!(overloads[0]) ==
25                       ToFunctionType!(string function())));
26     static assert( is(typeof(overloads[0]) == SymbolType!(overloads[0])));
27 
28     // void foo(string);
29     static assert( is(SymbolType!(overloads[1]) == function));
30     static assert(!__traits(compiles, PropertyType!(overloads[1])));
31     static assert( is(typeof(overloads[1]) == function));
32 
33     static assert( is(SymbolType!(overloads[1]) ==
34                       ToFunctionType!(void function(string))));
35     static assert( is(typeof(overloads[1]) == SymbolType!(overloads[1])));
36 
37     // void foo(string, int);
38     static assert( is(SymbolType!(overloads[2]) == function));
39     static assert(!__traits(compiles, PropertyType!(overloads[2])));
40     static assert( is(typeof(overloads[2]) == function));
41 
42     static assert( is(SymbolType!(overloads[2]) ==
43                       ToFunctionType!(bool function(string, int))));
44     static assert( is(typeof(overloads[2]) == SymbolType!(overloads[2])));
45 }
46 {
47     static assert( is(SymbolType!(S.bar) ==
48                       ToFunctionType!(void function(int) @property)));
49     static assert( is(PropertyType!(S.bar) == int));
50     static assert( is(typeof(S.bar) == PropertyType!(S.bar)));
51 
52     alias overloads = __traits(getOverloads, S, "bar");
53 
54     // @property void bar(int);
55     static assert( is(SymbolType!(overloads[0]) == function));
56     static assert(!__traits(compiles, PropertyType!(overloads[0])));
57     static assert(!__traits(compiles, typeof(overloads[0])));
58 
59     static assert( is(SymbolType!(overloads[0]) ==
60                       ToFunctionType!(void function(int) @property)));
61 
62     // @property int bar();
63     static assert( is(SymbolType!(overloads[1]) == function));
64     static assert( is(PropertyType!(overloads[1]) == int));
65     static assert( is(typeof(overloads[1]) == PropertyType!(overloads[1])));
66 
67     static assert( is(SymbolType!(overloads[1]) ==
68                       ToFunctionType!(int function() @property)));
69 }

See Also

Meta