Commit 515d2b9
authored
Add SymbolType, PropertyType, and ToFunctionType to phobos.sys.traits. (dlang#10805)
SymbolType and PropertyType are completely new. They're both wrappers
around typeof, and the idea is to work around the fact that typeof(foo)
is syntactically ambiguous. Specifically, depending on the context, you
want typeof(foo) to give you the type of foo as an expression, or you
want typeof(foo) to give you the type of foo as a symbol.
For non-functions, this is a non-issue, because the type of the symbol
and the type of the expression when using the symbol on its own in an
expression are the same. A variable of type int has the type of int, and
if you use the variable in an expression - e.g. returning it from a
function - then that expression also has the type of int.
However, for functions, the type of the expression and the type of the
symbol are not the same.
int foo();
has the type of int(), but when it's used in expression - e.g.
auto bar() { return foo; } - the type is int.
If functions always had to be called with parentheses, this would not be
an issue, because then typeof(foo) could not be a function call, and
therefore it would have to be the type of the symbol, whereas
typeof(foo()) would be the type of the expression. However, while
typeof(foo()) is clearly an expression, because of optional parens,
typeof(foo) could be either the type of the symbol or the expression.
And @Property makes the situation even worse.
The compiler _could_ of course give an error due to the ambiguity, but
it doesn't (and that would cause its own set of problems anyway).
Rather, if the function is not marked with @Property, then typeof(foo)
gives the type of the symbol, whereas if it is marked with @Property,
then typeof(foo) gives the type of the expression - i.e. the type that
you get when calling the function (which results in an error if the
function cannot be called with no arguments or returns void, since the
expression is then invalid). The theory behind that behavior was that
because an @Property function is emulating a member variable, typeof
should treat it like a member variable so that the result would be the
same whether the property was a variable or a function. As such, typeof
gives the same type as it would have if it were a member variable, which
means giving the type the expression has when the property is used on
its own (and thus is called).
However, in practice, this is a mess, because in practice, whether you
want the type of the expression or the type of the symbol actually
depends on what the code is doing, not on whether the function is
supposed to act like a member variable or not. In addition, because we
still have optional parens in spite of having @Property, it means that
how a function is used actually has nothing to do with @Property. foo
could be a variable, an @Property function, or a non-@Property function,
and `return foo;` would work just fine so long as foo is not a function
which requires arguments and so long as it has a return type.
So, in a case where the code is going to use foo in an expression,
you're probably going to want typeof(foo) to give you the type of foo as
an expression regardless of whether it's a varable, an @Property
function, a non-@Property function, or any other symbol with a type.
However, if you're trying to get information on the symbol itself (e.g.
because the code actually needs to know whether it's a function, or
because it needs to get the function's attributes), then you want
typeof(foo) to give the type of the symbol regardless of what that
symbol is. But there is no way to tell typeof which behavior you want.
Rather, its behavior differs depending on the type of the symbol and on
whether it's been marked with @Property. This means that typeof is
inherently error-prone.
std.traits has had to work around that in a number of places, and who
knows how much generic code exists in the wild which happens to work
correctly with the symbols that it's been tested with but which would
fail if given something else (e.g. if every function that a piece of
templated code currently operates on is an @Property function, it could
easily have assumed that typeof(foo) gave the type of the expression and
thus would fail when given a non-@Property function, and it likely
wouldn't be obvious to the programmer without testing).
What this means is that ideally, the language would have something like
typeof_expr and typeof_sym instead of typeof, but it's not like we're
going to get rid of typeof at this stage, and adding variants of it like
that would mean adding new keywords. So, that's probably never going to
happen.
So, to try to fix this problem for Phobos v3, we're adding the traits
SymbolType and PropertyType.
SymbolType will give the type of the actual symbol, so it will be used
in contexts where the introspection is beind done on the symbol itself.
PropertyType on the other hand will give the type of the symbol as an
expression. It's PropertyType rather than ExpressionType, because it
does not work on general expressions (because template alias parameters
only accept symbols), and the only situation where a function is a valid
expression on its own is if it can be used as a getter property (i.e. it
can be called with no arguments and returns a value). So, in effect,
PropertyType treats all functions as if they were marked with
@Property. The ones that can be used as getter properties will then give
their return types, and the others won't compile with PropertyType
(just like typeof won't compile with setter @Property functions, because
they're not valid expressions on their own).
So, SymbolType will then be used in cases where the actual type of the
symbol is needed, and PropertyType will be used in cases wher a symbol
is going to be used as a getter property, and the code needs to know
what type it will have in an expression without caring whether it's a
variable, an enum, a function, or whatever.
Having these two traits will make it possible for other traits to not
have to work around @Property like a number of them currently do in
std.traits. Rather, the programmer will indicate which they need in a
given situation by choosing SymbolType or PropertyType and pass that
result to whatever other trait or is expression which tells them what
they're trying to find out. So, we'll simultaneously be putting the
choice in the hands of the programmer and simplifying the other traits.
Furthermore, the plan here is that the function-related traits in
phobos.sys.traits will operate solely on types (except in situations
where the actual symbol is required - e.g. to get the names of
parameters), like most traits typically do. std.traits has a number of
its function-related traits operate on symbols in part to work around
the issue with @Property functions (as well as stuff like trying to
treat variables with opCall as functions instead of requiring that the
symbol for opCall itself be passed). And that not only makes the traits
more complicated, but in general, having traits which operate on both
types and symbols which aren't types makes the code error-prone and
harder to understand, since it's harder to see what the code is actually
operating on (and can have unintended consequences whenever an AliasSeq
mixes types and other symbols).
Having SymbolType and PropertyType will have the benefit of reducing the
number of cases where traits operate on both types and other symbols. It
should also help with clarity when a trait doesn't try to handle
everything itself. The programmer will therefore have full control over
the behavior that they get, because they can use typeof, SymbolType, or
PropertyType depending on what they're trying to do and what information
they want to get about the symbol or expression from other traits.
Exactly how this will affect each trait will of course depend on the
trait in question, but the idea is to simplify things and ultimately end
up with traits which are easier to understand and less error-prone. And
those traits will require less "magic" and special-casing, because the
programmer will have already dealt with whether they want the type of
the symbol or the type of the expression when getting the type to
instantiate the trait with.
The other new symbol, ToFunctionType, is a template which converts
function types, function pointer types, and delegate types to the
corresponding function type. So, something like `int function(string)`
or `int delegate(string)` would become `int(string)`. This is primarily
useful in implementing other function-related traits, but it also
provides a way to get function types to use in is expressions for
testing or providing examples (since it's not possible to write out a
function type in D outside of an actual function declaraton /
definition).
In terms of functionality, ToFunctionType is a replacement for
std.traits.FunctionTypeOf. FunctionTypeOf attempts to operate on
anything that's "callable" (both types and symbols), which makes it a
bit of a mess (and which actually cannot work in some cases due to
templated types or templated functions not having been instantiated).
And looking over where it's used in std.traits, it's usually used on
types anyway.
So, ToFunctionType operates exclusively on types. The change in name is
because FunctionTypeOf definitely sounds like a trait that's operating
on a symbol to get its type (even if it also accepts types), whereas
ToFunctionType sounds much more like it's converting the given type,
which is what it's doing.
Also, because some tests needed some module-level functions, this adds
the PhobosUnittest version identifier to the build. Phobos v2 has
StdUnittest, but it seemed kind of silly to use that, since this isn't
std, but we can change it later if we want to. And actually, on that
note, some of the phobos.sys documentation currently uses StdDdoc when
it should probably use something like PhobosDdoc, but the documentation
build still needs to be sorted out anyway.1 parent 96458a1 commit 515d2b9
2 files changed
+3162
-3
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
73 | 74 | | |
74 | 75 | | |
75 | 76 | | |
| |||
0 commit comments