2019-02-22 00:29:26 +00:00
/ * *
* Generic tagged union and algebraic data type implementations .
*
* Copyright : Copyright 2015 - 2019 , Sönke Ludwig .
* License : $ ( WEB www . boost . org / LICENSE_1_0 . txt , Boost License 1.0 ) .
* Authors : Sönke Ludwig
* /
module taggedalgebraic.taggedunion ;
import std.algorithm.mutation : move , swap ;
import std.meta ;
import std.traits : EnumMembers , FieldNameTuple , Unqual , isInstanceOf ;
2019-02-22 01:28:43 +00:00
2019-02-22 00:29:26 +00:00
/ * * Implements a generic tagged union type .
This struct takes a `union` or `struct` declaration as an input and builds
an algebraic data type from its fields , using an automatically generated
`Kind` enumeration to identify which field of the union is currently used .
Multiple fields with the same value are supported .
For each field defined by `U` a number of convenience members are generated .
For a given field "foo" , these fields are :
$ ( UL
$ ( LI `static foo(value)` ) - returns a new tagged union with the specified value )
$ ( LI `isFoo` - equivalent to `kind == Kind.foo` )
$ ( LI `setFoo(value)` - equivalent to `set!(Kind.foo)(value)` )
$ ( LI `getFoo` - equivalent to `get!(Kind.foo)` )
)
* /
struct TaggedUnion ( U ) if ( is ( U = = union ) | | is ( U = = struct ) | | is ( U = = enum ) )
{
import std.traits : FieldTypeTuple , FieldNameTuple , Largest ,
hasElaborateCopyConstructor , hasElaborateDestructor , isCopyable ;
import std.ascii : toUpper ;
alias FieldDefinitionType = U ;
/// A type enum that identifies the type of value currently stored.
alias Kind = UnionFieldEnum ! U ;
alias FieldTypes = UnionKindTypes ! Kind ;
alias fieldNames = UnionKindNames ! Kind ;
static assert ( FieldTypes . length > 0 , "The TaggedUnions's union type must have at least one field." ) ;
static assert ( FieldTypes . length = = fieldNames . length ) ;
package alias FieldTypeByName ( string name ) = FieldTypes [ __traits ( getMember , Kind , name ) ] ;
private {
2019-02-22 15:11:21 +00:00
static if ( isUnitType ! ( FieldTypes [ 0 ] ) | | __VERSION__ < 2072 ) {
2019-02-22 00:29:26 +00:00
void [ Largest ! FieldTypes . sizeof ] m_data ;
} else {
union Dummy {
FieldTypes [ 0 ] initField ;
void [ Largest ! FieldTypes . sizeof ] data ;
alias data this ;
}
Dummy m_data = { initField : FieldTypes [ 0 ] . init } ;
}
Kind m_kind ;
}
this ( TaggedUnion other )
{
rawSwap ( this , other ) ;
}
void opAssign ( TaggedUnion other )
{
rawSwap ( this , other ) ;
}
2019-02-22 15:11:21 +00:00
static foreach ( ti ; UniqueTypes ! FieldTypes )
static if ( ! isUnitType ! ( FieldTypes [ ti ] ) ) {
this ( FieldTypes [ ti ] value )
{
set ! ( cast ( Kind ) ti ) ( value ) ;
}
void opAssign ( FieldTypes [ ti ] value )
{
set ! ( cast ( Kind ) ti ) ( value ) ;
}
}
2019-02-22 00:29:26 +00:00
// disable default construction if first type is not a null/Void type
2019-02-22 15:11:21 +00:00
static if ( ! isUnitType ! ( FieldTypes [ 0 ] ) & & __VERSION__ < 2072 ) {
2019-02-22 00:29:26 +00:00
@disable this ( ) ;
}
// postblit constructor
static if ( ! allSatisfy ! ( isCopyable , FieldTypes ) ) {
@disable this ( this ) ;
} else static if ( anySatisfy ! ( hasElaborateCopyConstructor , FieldTypes ) ) {
this ( this )
{
switch ( m_kind ) {
default : break ;
foreach ( i , tname ; fieldNames ) {
alias T = FieldTypes [ i ] ;
static if ( hasElaborateCopyConstructor ! T )
{
case __traits ( getMember , Kind , tname ) :
typeid ( T ) . postblit ( cast ( void * ) & trustedGet ! T ( ) ) ;
return ;
}
}
}
}
}
// destructor
static if ( anySatisfy ! ( hasElaborateDestructor , FieldTypes ) ) {
~ this ( )
{
final switch ( m_kind ) {
foreach ( i , tname ; fieldNames ) {
alias T = FieldTypes [ i ] ;
case __traits ( getMember , Kind , tname ) :
static if ( hasElaborateDestructor ! T ) {
. destroy ( trustedGet ! T ) ;
}
return ;
}
}
}
}
/// Enables conversion or extraction of the stored value.
T opCast ( T ) ( )
{
import std.conv : to ;
final switch ( m_kind ) {
foreach ( i , FT ; FieldTypes ) {
case __traits ( getMember , Kind , fieldNames [ i ] ) :
static if ( is ( typeof ( trustedGet ! FT ) : T ) )
return trustedGet ! FT ;
else static if ( is ( typeof ( to ! T ( trustedGet ! FT ) ) ) ) {
return to ! T ( trustedGet ! FT ) ;
} else {
assert ( false , "Cannot cast a " ~ fieldNames [ i ]
~ " value of type " ~ FT . stringof ~ " to " ~ T . stringof ) ;
}
}
}
assert ( false ) ; // never reached
}
/// ditto
T opCast ( T ) ( ) const
{
// this method needs to be duplicated because inout doesn't work with to!()
import std.conv : to ;
final switch ( m_kind ) {
foreach ( i , FT ; FieldTypes ) {
case __traits ( getMember , Kind , fieldNames [ i ] ) :
static if ( is ( typeof ( trustedGet ! FT ) : T ) )
return trustedGet ! FT ;
else static if ( is ( typeof ( to ! T ( trustedGet ! FT ) ) ) ) {
return to ! T ( trustedGet ! FT ) ;
} else {
assert ( false , "Cannot cast a " ~ fieldNames [ i ]
~ " value of type" ~ FT . stringof ~ " to " ~ T . stringof ) ;
}
}
}
assert ( false ) ; // never reached
}
/// Enables equality comparison with the stored value.
bool opEquals ( ) ( auto ref inout ( TaggedUnion ) other )
inout {
if ( this . kind ! = other . kind ) return false ;
final switch ( this . kind ) {
foreach ( i , fname ; TaggedUnion ! U . fieldNames )
case __traits ( getMember , Kind , fname ) :
return trustedGet ! ( FieldTypes [ i ] ) = = other . trustedGet ! ( FieldTypes [ i ] ) ;
}
assert ( false ) ; // never reached
}
/// The type ID of the currently stored value.
@property Kind kind ( ) const { return m_kind ; }
static foreach ( i , name ; fieldNames ) {
// NOTE: using getX/setX here because using just x would be prone to
// misuse (attempting to "get" a value for modification when
// a different kind is set instead of assigning a new value)
mixin ( "alias set" ~ pascalCase ( name ) ~ " = set!(Kind." ~ name ~ ");" ) ;
mixin ( "@property bool is" ~ pascalCase ( name ) ~ "() const { return m_kind == Kind." ~ name ~ "; }" ) ;
2019-02-22 15:11:21 +00:00
static if ( ! isUnitType ! ( FieldTypes [ i ] ) ) {
mixin ( "alias " ~ name ~ "Value = value!(Kind." ~ name ~ ");" ) ;
2019-02-22 00:29:26 +00:00
mixin ( "static TaggedUnion " ~ name ~ "(FieldTypes[" ~ i . stringof ~ "] value)"
~ "{ TaggedUnion tu; tu.set!(Kind." ~ name ~ ")(move(value)); return tu; }" ) ;
// TODO: define assignment operator for unique types
} else {
mixin ( "static @property TaggedUnion " ~ name ~ "() { TaggedUnion tu; tu.set!(Kind." ~ name ~ "); return tu; }" ) ;
}
}
2019-02-22 15:11:21 +00:00
/ * * Accesses the contained value by reference .
The specified `kind` must equal the current value of the `this.kind`
property . Setting a different type must be done with `set` or `opAssign`
instead .
See_Also : `set` , `opAssign`
* /
@property ref inout ( FieldTypes [ kind ] ) value ( Kind kind ) ( )
2019-02-22 00:29:26 +00:00
inout {
if ( this . kind ! = kind ) {
enum msg ( . string k_is ) = "Attempt to get kind " ~ kind . stringof ~ " from tagged union with kind " ~ k_is ;
final switch ( this . kind ) {
static foreach ( i , n ; fieldNames )
case __traits ( getMember , Kind , n ) :
assert ( false , msg ! n ) ;
}
}
//return trustedGet!(FieldTypes[kind]);
return * ( ) @trusted { return cast ( const ( FieldTypes [ kind ] ) * ) m_data . ptr ; } ( ) ;
}
2019-02-22 15:11:21 +00:00
/ * * Accesses the contained value by reference .
The specified type `T` must equal the type of the currently set value .
Setting a different type must be done with `set` or `opAssign` instead .
See_Also : `set` , `opAssign`
* /
@property ref inout ( T ) value ( T ) ( ) inout
2019-02-22 00:29:26 +00:00
if ( staticIndexOf ! ( T , FieldTypes ) > = 0 )
{
final switch ( this . kind ) {
static foreach ( n ; fieldNames ) {
case __traits ( getMember , Kind , n ) :
static if ( is ( FieldTypes [ __traits ( getMember , Kind , n ) ] = = T ) )
return trustedGet ! T ;
else assert ( false , "Attempting to get type " ~ T . stringof
~ " from a TaggedUnion with type "
~ FieldTypes [ __traits ( getMember , Kind , n ) ] . stringof ) ;
}
}
}
2019-02-22 15:11:21 +00:00
/ * * Sets a new value of the specified `kind` .
* /
2019-02-22 00:29:26 +00:00
ref FieldTypes [ kind ] set ( Kind kind ) ( FieldTypes [ kind ] value )
2019-02-22 15:11:21 +00:00
if ( ! isUnitType ! ( FieldTypes [ kind ] ) )
2019-02-22 00:29:26 +00:00
{
if ( m_kind ! = kind ) {
destroy ( this ) ;
m_data . rawEmplace ( value ) ;
} else {
rawSwap ( trustedGet ! ( FieldTypes [ kind ] ) , value ) ;
}
m_kind = kind ;
return trustedGet ! ( FieldTypes [ kind ] ) ;
}
2019-02-22 15:11:21 +00:00
/ * * Sets a `void` value of the specified kind .
* /
2019-02-22 00:29:26 +00:00
void set ( Kind kind ) ( )
2019-02-22 15:11:21 +00:00
if ( isUnitType ! ( FieldTypes [ kind ] ) )
2019-02-22 00:29:26 +00:00
{
if ( m_kind ! = kind ) {
destroy ( this ) ;
}
m_kind = kind ;
}
package @trusted @property ref inout ( T ) trustedGet ( T ) ( ) inout { return * cast ( inout ( T ) * ) m_data . ptr ; }
}
///
@safe nothrow unittest {
union Kinds {
int count ;
string text ;
}
alias TU = TaggedUnion ! Kinds ;
// default initialized to the first field defined
TU tu ;
assert ( tu . kind = = TU . Kind . count ) ;
assert ( tu . isCount ) ; // qequivalent to the line above
assert ( ! tu . isText ) ;
2019-02-22 15:11:21 +00:00
assert ( tu . value ! ( TU . Kind . count ) = = int . init ) ;
2019-02-22 00:29:26 +00:00
// set to a specific count
tu . setCount ( 42 ) ;
assert ( tu . isCount ) ;
2019-02-22 15:11:21 +00:00
assert ( tu . countValue = = 42 ) ;
assert ( tu . value ! ( TU . Kind . count ) = = 42 ) ;
assert ( tu . value ! int = = 42 ) ; // can also get by type
assert ( tu . countValue = = 42 ) ;
2019-02-22 00:29:26 +00:00
// assign a new tagged algebraic value
tu = TU . count ( 43 ) ;
// test equivalence with other tagged unions
assert ( tu = = TU . count ( 43 ) ) ;
assert ( tu ! = TU . count ( 42 ) ) ;
assert ( tu ! = TU . text ( "hello" ) ) ;
// modify by reference
2019-02-22 15:11:21 +00:00
tu . countValue + + ;
assert ( tu . countValue = = 44 ) ;
2019-02-22 00:29:26 +00:00
// set the second field
tu . setText ( "hello" ) ;
assert ( ! tu . isCount ) ;
assert ( tu . isText ) ;
assert ( tu . kind = = TU . Kind . text ) ;
2019-02-22 15:11:21 +00:00
assert ( tu . textValue = = "hello" ) ;
// unique types can also be directly constructed
tu = TU ( 12 ) ;
assert ( tu . countValue = = 12 ) ;
tu = TU ( "foo" ) ;
assert ( tu . textValue = = "foo" ) ;
2019-02-22 00:29:26 +00:00
}
///
@safe nothrow unittest {
// Enum annotations supported since DMD 2.082.0. The mixin below is
// necessary to keep the parser happy on older versions.
static if ( __VERSION__ > = 2082 ) {
alias myint = int ;
// tagged unions can be defined in terms of an annotated enum
mixin ( q{ enum E {
none ,
@string text
} } ) ;
alias TU = TaggedUnion ! E ;
static assert ( is ( TU . Kind = = E ) ) ;
TU tu ;
assert ( tu . isNone ) ;
assert ( tu . kind = = E . none ) ;
tu . setText ( "foo" ) ;
assert ( tu . kind = = E . text ) ;
2019-02-22 15:11:21 +00:00
assert ( tu . textValue = = "foo" ) ;
2019-02-22 00:29:26 +00:00
}
}
unittest { // test for name clashes
union U { . string string ; }
alias TU = TaggedUnion ! U ;
TU tu ;
tu = TU . string ( "foo" ) ;
assert ( tu . isString ) ;
2019-02-22 15:11:21 +00:00
assert ( tu . stringValue = = "foo" ) ;
2019-02-22 00:29:26 +00:00
}
2019-02-22 01:28:43 +00:00
/ * * Dispatches the value contained on a `TaggedUnion` to a set of visitors .
A visitor can have one of three forms :
$ ( UL
$ ( LI function or delegate taking a single typed parameter )
$ ( LI function or delegate taking no parameters )
$ ( LI function or delegate template taking any single parameter )
)
. . . .
* /
2019-02-22 11:22:38 +00:00
template visit ( VISITORS . . . )
if ( VISITORS . length > 0 )
{
2019-02-22 01:28:43 +00:00
auto visit ( TU ) ( auto ref TU tu )
if ( isInstanceOf ! ( TaggedUnion , TU ) )
{
final switch ( tu . kind ) {
static foreach ( k ; EnumMembers ! ( TU . Kind ) ) {
case k : {
2019-02-22 15:11:21 +00:00
static if ( isUnitType ! ( TU . FieldTypes [ k ] ) )
2019-02-22 01:28:43 +00:00
alias T = void ;
else alias T = TU . FieldTypes [ k ] ;
alias h = selectHandler ! ( T , VISITORS ) ;
2019-02-22 10:14:56 +00:00
static if ( is ( typeof ( h ) = = typeof ( null ) ) ) static assert ( false , "No visitor defined for type type " ~ T . stringof ) ;
2019-02-22 01:28:43 +00:00
else static if ( is ( typeof ( h ) = = string ) ) static assert ( false , h ) ;
else static if ( is ( T = = void ) ) return h ( ) ;
2019-02-22 15:11:21 +00:00
else return h ( tu . value ! k ) ;
2019-02-22 01:28:43 +00:00
}
}
}
}
}
///
unittest {
union U {
int number ;
string text ;
}
alias TU = TaggedUnion ! U ;
auto tu = TU . number ( 42 ) ;
tu . visit ! (
( int n ) { assert ( n = = 42 ) ; } ,
( string s ) { assert ( false ) ; }
) ;
assert ( tu . visit ! ( ( v ) = > to ! int ( v ) ) = = 42 ) ;
tu . setText ( "43" ) ;
assert ( tu . visit ! ( ( v ) = > to ! int ( v ) ) = = 43 ) ;
}
2019-02-22 10:14:56 +00:00
unittest {
union U {
Void none ;
int count ;
float length ;
}
TaggedUnion ! U u ;
//
static assert ( is ( typeof ( u . visit ! ( ( int ) { } , ( float ) { } , ( ) { } ) ) ) ) ;
u . visit ! ( ( _ ) { } , ( ) { } ) ;
static assert ( is ( typeof ( u . visit ! ( ( _ ) { } , ( ) { } ) ) ) ) ;
static assert ( is ( typeof ( u . visit ! ( ( _ ) { } , ( float ) { } , ( ) { } ) ) ) ) ;
static assert ( is ( typeof ( u . visit ! ( ( float ) { } , ( _ ) { } , ( ) { } ) ) ) ) ;
static assert ( ! is ( typeof ( u . visit ! ( ( _ ) { } ) ) ) ) ; // missing void handler
static assert ( ! is ( typeof ( u . visit ! ( ( ) { } ) ) ) ) ; // missing value handler
static assert ( ! is ( typeof ( u . visit ! ( ( _ ) { } , ( ) { } , ( string ) { } ) ) ) ) ; // invalid typed handler
static assert ( ! is ( typeof ( u . visit ! ( ( int ) { } , ( float ) { } , ( ) { } , ( ) { } ) ) ) ) ; // duplicate void handler
static assert ( ! is ( typeof ( u . visit ! ( ( _ ) { } , ( ) { } , ( _ ) { } ) ) ) ) ; // duplicate generic handler
static assert ( ! is ( typeof ( u . visit ! ( ( int ) { } , ( float ) { } , ( float ) { } , ( ) { } ) ) ) ) ; // duplicate typed handler
// TODO: error out for superfluous generic handlers
//static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler
}
2019-02-22 01:28:43 +00:00
// workaround for "template to is not defined" error in the unit test above
// happens on DMD 2.080 and below
private U to ( U , T ) ( T val ) {
static import std.conv ;
return std . conv . to ! U ( val ) ;
}
/ * * The same as `visit` , except that failure to handle types is checked at runtime .
Instead of failing to compile , `tryVisit` will throw an `Exception` if none
of the handlers is able to handle the value contained in `tu` .
* /
2019-02-22 11:22:38 +00:00
template tryVisit ( VISITORS . . . )
if ( VISITORS . length > 0 )
{
2019-02-22 01:28:43 +00:00
auto tryVisit ( TU ) ( auto ref TU tu )
if ( isInstanceOf ! ( TaggedUnion , TU ) )
{
final switch ( tu . kind ) {
static foreach ( k ; EnumMembers ! ( TU . Kind ) ) {
case k : {
2019-02-22 15:11:21 +00:00
static if ( isUnitType ! ( TU . FieldTypes [ k ] ) )
2019-02-22 01:28:43 +00:00
alias T = void ;
else alias T = TU . FieldTypes [ k ] ;
alias h = selectHandler ! ( T , VISITORS ) ;
2019-02-22 10:14:56 +00:00
static if ( is ( typeof ( h ) = = typeof ( null ) ) ) throw new Exception ( "Type " ~ T . stringof ~ " not handled by any visitor." ) ;
2019-02-22 01:28:43 +00:00
else static if ( is ( typeof ( h ) = = string ) ) static assert ( false , h ) ;
else static if ( is ( T = = void ) ) return h ( ) ;
2019-02-22 15:11:21 +00:00
else return h ( tu . value ! k ) ;
2019-02-22 01:28:43 +00:00
}
}
}
}
}
///
unittest {
import std.exception : assertThrown ;
union U {
int number ;
string text ;
}
alias TU = TaggedUnion ! U ;
auto tu = TU . number ( 42 ) ;
tu . tryVisit ! ( ( int n ) { assert ( n = = 42 ) ; } ) ;
assertThrown ( tu . tryVisit ! ( ( string s ) { assert ( false ) ; } ) ) ;
}
2019-02-22 15:11:21 +00:00
enum isUnitType ( T ) = is ( T = = Void ) | | is ( T = = void ) | | is ( T = = typeof ( null ) ) ;
2019-02-22 00:29:26 +00:00
2019-02-22 10:14:56 +00:00
private template validateHandlers ( TU , VISITORS . . . )
{
alias Types = TU . FieldTypes ;
static foreach ( int i ; 0 . . VISITORS . length ) {
static assert ( anySatisfy ! ( matchesType ! ( VISITORS [ i ] ) , Types ) ,
"Visitor at index " ~ i . stringof ~ " does not match any type of " ~ TU . stringof ) ;
}
}
private template matchesType ( alias fun , T )
{
static if ( isSomeFunction ! fun ) {
alias Params = ParameterTypeTuple ! fun ;
static if ( Params . length = = 0 & & is ( T = = void ) ) enum matchesType = true ;
else static if ( Params . length = = 1 & & is ( T = = Params [ 0 ] ) ) enum matchesType = true ;
else enum matchesType = false ;
} else static if ( ! is ( T = = void ) ) {
static if ( isSomeFunction ! ( fun ! T ) ) {
alias Parms = ParameterTypeTuple ! fun ;
static if ( Params . length = = 1 & & is ( T = = Params [ 0 ] ) ) enum matchesType = true ;
else enum matchesType = false ;
} else enum matchesType = false ;
} else enum matchesType = false ;
}
2019-02-22 01:28:43 +00:00
private template selectHandler ( T , VISITORS . . . )
{
import std.traits : ParameterTypeTuple , isSomeFunction ;
2019-02-22 10:14:56 +00:00
template typedIndex ( int i , int matched_index = - 1 ) {
2019-02-22 01:28:43 +00:00
static if ( i < VISITORS . length ) {
alias fun = VISITORS [ i ] ;
static if ( isSomeFunction ! fun ) {
alias Params = ParameterTypeTuple ! fun ;
2019-02-22 10:14:56 +00:00
static if ( Params . length > 1 ) enum typedIndex = "Visitor at index " ~ i . stringof ~ " must not take more than one parameter." ;
else static if ( Params . length = = 0 & & is ( T = = void ) | | Params . length = = 1 & & is ( T = = Params [ 0 ] ) ) {
static if ( matched_index > = 0 ) enum typedIndex = "Vistor at index " ~ i . stringof ~ " conflicts with visitor at index " ~ matched_index ~ "." ;
else enum typedIndex = typedIndex ! ( i + 1 , i ) ;
} else enum typedIndex = typedIndex ! ( i + 1 , matched_index ) ;
} else enum typedIndex = typedIndex ! ( i + 1 , matched_index ) ;
} else enum typedIndex = matched_index ;
2019-02-22 01:28:43 +00:00
}
2019-02-22 10:14:56 +00:00
template genericIndex ( int i , int matched_index = - 1 ) {
static if ( i < VISITORS . length ) {
alias fun = VISITORS [ i ] ;
static if ( ! isSomeFunction ! fun ) {
static if ( isSomeFunction ! ( fun ! T ) ) {
static if ( ParameterTypeTuple ! ( fun ! T ) . length = = 1 ) {
static if ( matched_index > = 0 ) enum genericIndex = "Only one generic visitor allowed" ;
else enum genericIndex = genericIndex ! ( i + 1 , i ) ;
} else enum genericIndex = "Generic visitor at index " ~ i . stringof ~ " must have a single parameter." ;
} else enum genericIndex = "Visitor at index " ~ i . stringof ~ " (or its template instantiation with type " ~ T . stringof ~ ") must be a valid function or delegate." ;
} else enum genericIndex = genericIndex ! ( i + 1 , matched_index ) ;
} else enum genericIndex = matched_index ;
}
enum typed_index = typedIndex ! 0 ;
static if ( is ( T = = void ) ) enum generic_index = - 1 ;
else enum generic_index = genericIndex ! 0 ;
static if ( is ( typeof ( typed_index ) = = string ) ) enum selectHandler = typed_index ;
else static if ( is ( typeof ( generic_index = = string ) ) ) enum selectHandler = generic_index ;
else static if ( typed_index > = 0 ) alias selectHandler = VISITORS [ typed_index ] ;
else static if ( generic_index > = 0 ) alias selectHandler = VISITORS [ generic_index ] ;
else enum selectHandler = null ;
2019-02-22 01:28:43 +00:00
}
2019-02-22 00:29:26 +00:00
private string pascalCase ( string camel_case )
{
if ( ! __ctfe ) assert ( false ) ;
import std.ascii : toUpper ;
return camel_case [ 0 ] . toUpper ~ camel_case [ 1 . . $ ] ;
}
2019-02-22 16:34:08 +00:00
/ * * Maps a kind enumeration value to the corresponding field type .
2019-02-22 00:29:26 +00:00
2019-02-22 16:34:08 +00:00
`kind` must be a value of the `TaggedAlgebraic!T.Kind` enumeration .
* /
template TypeOf ( alias kind )
if ( is ( typeof ( kind ) = = enum ) )
{
import std.traits : FieldTypeTuple , TemplateArgsOf ;
import std.typecons : ReplaceType ;
static if ( isInstanceOf ! ( UnionFieldEnum , typeof ( kind ) ) ) {
alias U = TemplateArgsOf ! ( typeof ( kind ) ) ;
alias FT = FieldTypeTuple ! U [ kind ] ;
} else {
alias U = typeof ( kind ) ;
alias Types = UnionKindTypes ! ( typeof ( kind ) ) ;
alias uda = AliasSeq ! ( __traits ( getAttributes , kind ) ) ;
static if ( uda . length = = 0 ) alias FT = void ;
else alias FT = uda [ 0 ] ;
2019-02-22 00:29:26 +00:00
}
2019-02-22 16:34:08 +00:00
alias TypeOf = ReplaceType ! ( This , U , FT ) ;
}
2019-02-22 00:29:26 +00:00
2019-02-22 16:34:08 +00:00
///
unittest {
static struct S {
int a ;
string b ;
string c ;
2019-02-22 00:29:26 +00:00
}
2019-02-22 16:34:08 +00:00
alias TU = TaggedUnion ! S ;
static assert ( is ( TypeOf ! ( TU . Kind . a ) = = int ) ) ;
static assert ( is ( TypeOf ! ( TU . Kind . b ) = = string ) ) ;
static assert ( is ( TypeOf ! ( TU . Kind . c ) = = string ) ) ;
}
unittest {
struct S {
TaggedUnion ! This [ ] test ;
}
alias TU = TaggedUnion ! S ;
TypeOf ! ( TU . Kind . test ) a ;
static assert ( is ( TypeOf ! ( TU . Kind . test ) = = TaggedUnion ! S [ ] ) ) ;
2019-02-22 00:29:26 +00:00
}
/// Convenience type that can be used for union fields that have no value (`void` is not allowed).
struct Void { }
2019-02-22 16:34:08 +00:00
/ * * Special type used as a placeholder for `U` within the definition of `U` to
enable self - referential types .
Note that this is recognized only if used as the first argument to a
template type .
* /
struct This { Void nothing ; }
///
unittest {
union U {
TaggedUnion ! This [ ] list ;
int number ;
string text ;
}
alias Node = TaggedUnion ! U ;
auto n = Node ( [ Node ( 12 ) , Node ( "foo" ) ] ) ;
assert ( n . isList ) ;
assert ( n . listValue = = [ Node ( 12 ) , Node ( "foo" ) ] ) ;
}
2019-02-22 00:29:26 +00:00
private template UnionFieldEnum ( U )
{
static if ( is ( U = = enum ) ) alias UnionFieldEnum = U ;
else {
import std.array : join ;
import std.traits : FieldNameTuple ;
mixin ( "enum UnionFieldEnum { " ~ [ FieldNameTuple ! U ] . join ( ", " ) ~ " }" ) ;
}
}
deprecated alias TypeEnum ( U ) = UnionFieldEnum ! U ;
private alias UnionKindTypes ( FieldEnum ) = staticMap ! ( TypeOf , EnumMembers ! FieldEnum ) ;
private alias UnionKindNames ( FieldEnum ) = AliasSeq ! ( __traits ( allMembers , FieldEnum ) ) ;
2019-02-22 15:11:21 +00:00
private template UniqueTypes ( Types . . . ) {
template impl ( size_t i ) {
static if ( i < Types . length ) {
alias T = Types [ i ] ;
static if ( staticIndexOf ! ( T , Types ) = = i & & staticIndexOf ! ( T , Types [ i + 1 . . $ ] ) < 0 )
alias impl = AliasSeq ! ( i , impl ! ( i + 1 ) ) ;
else alias impl = AliasSeq ! ( impl ! ( i + 1 ) ) ;
} else alias impl = AliasSeq ! ( ) ;
}
alias UniqueTypes = impl ! 0 ;
}
private template AmbiguousTypes ( Types . . . ) {
template impl ( size_t i ) {
static if ( i < Types . length ) {
alias T = Types [ i ] ;
static if ( staticIndexOf ! ( T , Types ) = = i & & staticIndexOf ! ( T , Types [ i + 1 . . $ ] ) > = 0 )
alias impl = AliasSeq ! ( i , impl ! ( i + 1 ) ) ;
else alias impl = impl ! ( i + 1 ) ;
} else alias impl = AliasSeq ! ( ) ;
}
alias AmbiguousTypeFields = impl ! 0 ;
}
2019-02-22 00:29:26 +00:00
package void rawEmplace ( T ) ( void [ ] dst , ref T src )
{
T [ ] tdst = ( ) @trusted { return cast ( T [ ] ) dst [ 0 . . T . sizeof ] ; } ( ) ;
static if ( is ( T = = class ) ) {
tdst [ 0 ] = src ;
} else {
import std.conv : emplace ;
emplace ! T ( & tdst [ 0 ] ) ;
tdst [ 0 ] = src ;
}
}
// std.algorithm.mutation.swap sometimes fails to compile due to
// internal errors in hasElaborateAssign!T/isAssignable!T. This is probably
// caused by cyclic dependencies. However, there is no reason to do these
// checks in this context, so we just directly move the raw memory.
package void rawSwap ( T ) ( ref T a , ref T b )
@trusted {
void [ T . sizeof ] tmp = void ;
void [ ] ab = ( cast ( void * ) & a ) [ 0 . . T . sizeof ] ;
void [ ] bb = ( cast ( void * ) & b ) [ 0 . . T . sizeof ] ;
tmp [ ] = ab [ ] ;
ab [ ] = bb [ ] ;
bb [ ] = tmp [ ] ;
}