1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Reflection ;
5+ using System . Reflection . Emit ;
6+
7+ namespace Dasher . TypeProviders
8+ {
9+ public sealed class UnionProvider : ITypeProvider
10+ {
11+ // Union types are serialised as an array of two values:
12+ // The string name of the type, including namespace and any generic type parameters
13+ // The serialised value, as per regular Dasher serialisation
14+
15+ public bool CanProvide ( Type type )
16+ => type . IsGenericType &&
17+ type . GetGenericTypeDefinition ( ) . Namespace == nameof ( Dasher ) &&
18+ type . GetGenericTypeDefinition ( ) . Name . StartsWith ( $ "{ nameof ( Union < int , int > ) } `") ;
19+
20+ public void Serialise ( ILGenerator ilg , LocalBuilder value , LocalBuilder packer , LocalBuilder contextLocal , DasherContext context )
21+ {
22+ // write header
23+ ilg . Emit ( OpCodes . Ldloc , packer ) ;
24+ ilg . Emit ( OpCodes . Ldc_I4_2 ) ;
25+ ilg . Emit ( OpCodes . Call , typeof ( UnsafePacker ) . GetMethod ( nameof ( UnsafePacker . PackArrayHeader ) ) ) ;
26+
27+ // TODO might be faster if we a generated class having members for use with called 'Union<>.Match'
28+
29+ var typeObj = ilg . DeclareLocal ( typeof ( Type ) ) ;
30+ ilg . Emit ( OpCodes . Ldloc , value ) ;
31+ ilg . Emit ( OpCodes . Callvirt , value . LocalType . GetProperty ( nameof ( Union < int , int > . Type ) ) . GetMethod ) ;
32+ ilg . Emit ( OpCodes . Stloc , typeObj ) ;
33+
34+ // write type name
35+ ilg . Emit ( OpCodes . Ldloc , packer ) ;
36+ ilg . Emit ( OpCodes . Ldloc , typeObj ) ;
37+ ilg . Emit ( OpCodes . Call , typeof ( UnionProvider ) . GetMethod ( nameof ( GetTypeName ) , BindingFlags . Static | BindingFlags . Public ) ) ;
38+ ilg . Emit ( OpCodes . Call , typeof ( UnsafePacker ) . GetMethod ( nameof ( UnsafePacker . Pack ) , new [ ] { typeof ( string ) } ) ) ;
39+
40+ // loop through types within the union, looking for a match
41+ var doneLabel = ilg . DefineLabel ( ) ;
42+ var labelNextType = ilg . DefineLabel ( ) ;
43+ foreach ( var type in value . LocalType . GetGenericArguments ( ) )
44+ {
45+ ilg . LoadType ( type ) ;
46+ ilg . Emit ( OpCodes . Ldloc , typeObj ) ;
47+ ilg . Emit ( OpCodes . Call , typeof ( object ) . GetMethod ( nameof ( object . Equals ) , BindingFlags . Static | BindingFlags . Public ) ) ;
48+
49+ // continue if this type doesn't match the union's values
50+ ilg . Emit ( OpCodes . Brfalse , labelNextType ) ;
51+
52+ // we have a match
53+
54+ // get the value
55+ var valueObj = ilg . DeclareLocal ( type ) ;
56+ ilg . Emit ( OpCodes . Ldloc , value ) ;
57+ ilg . Emit ( OpCodes . Callvirt , value . LocalType . GetProperty ( nameof ( Union < int , int > . Value ) ) . GetMethod ) ;
58+ ilg . Emit ( type . IsValueType ? OpCodes . Unbox_Any : OpCodes . Castclass , type ) ;
59+ ilg . Emit ( OpCodes . Stloc , valueObj ) ;
60+
61+ // write value
62+ if ( ! context . TrySerialise ( ilg , valueObj , packer , contextLocal ) )
63+ throw new Exception ( $ "Unable to serialise type { type } ") ;
64+
65+ ilg . Emit ( OpCodes . Br , doneLabel ) ;
66+
67+ ilg . MarkLabel ( labelNextType ) ;
68+ labelNextType = ilg . DefineLabel ( ) ;
69+ }
70+
71+ ilg . MarkLabel ( labelNextType ) ;
72+
73+ ilg . Emit ( OpCodes . Ldstr , "No match on union type" ) ;
74+ ilg . Emit ( OpCodes . Newobj , typeof ( Exception ) . GetConstructor ( new [ ] { typeof ( string ) } ) ) ;
75+ ilg . Emit ( OpCodes . Throw ) ;
76+
77+ ilg . MarkLabel ( doneLabel ) ;
78+ }
79+
80+ public void Deserialise ( ILGenerator ilg , string name , Type targetType , LocalBuilder value , LocalBuilder unpacker , LocalBuilder contextLocal , DasherContext context , UnexpectedFieldBehaviour unexpectedFieldBehaviour )
81+ {
82+ // read the array length
83+ var count = ilg . DeclareLocal ( typeof ( int ) ) ;
84+ ilg . Emit ( OpCodes . Ldloc , unpacker ) ;
85+ ilg . Emit ( OpCodes . Ldloca , count ) ;
86+ ilg . Emit ( OpCodes . Call , typeof ( Unpacker ) . GetMethod ( nameof ( Unpacker . TryReadArrayLength ) ) ) ;
87+
88+ var lbl0 = ilg . DefineLabel ( ) ;
89+ ilg . Emit ( OpCodes . Brtrue , lbl0 ) ;
90+ {
91+ ilg . Emit ( OpCodes . Ldstr , "Union values must be encoded as an array for property \" {0}\" of type \" {1}\" " ) ;
92+ ilg . Emit ( OpCodes . Ldstr , name ) ;
93+ ilg . LoadType ( value . LocalType ) ;
94+ ilg . Emit ( OpCodes . Call , typeof ( string ) . GetMethod ( nameof ( string . Format ) , new [ ] { typeof ( string ) , typeof ( object ) , typeof ( object ) } ) ) ;
95+ ilg . LoadType ( targetType ) ;
96+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
97+ ilg . Emit ( OpCodes . Throw ) ;
98+ }
99+ ilg . MarkLabel ( lbl0 ) ;
100+
101+ // ensure we have two items in the array
102+ var readValueLabel = ilg . DefineLabel ( ) ;
103+ ilg . Emit ( OpCodes . Ldloc , count ) ;
104+ ilg . Emit ( OpCodes . Ldc_I4_2 ) ;
105+ ilg . Emit ( OpCodes . Beq , readValueLabel ) ;
106+ {
107+ // throw due to incorrect number of items in Union array
108+ ilg . Emit ( OpCodes . Ldstr , "Union array should have 2 elements (not {0}) for property \" {1}\" of type \" {2}\" " ) ;
109+ ilg . Emit ( OpCodes . Ldloc , count ) ;
110+ ilg . Emit ( OpCodes . Box , typeof ( int ) ) ;
111+ ilg . Emit ( OpCodes . Ldstr , name ) ;
112+ ilg . LoadType ( value . LocalType ) ;
113+ ilg . Emit ( OpCodes . Call , typeof ( string ) . GetMethod ( nameof ( string . Format ) , new [ ] { typeof ( string ) , typeof ( object ) , typeof ( object ) , typeof ( object ) } ) ) ;
114+ ilg . LoadType ( targetType ) ;
115+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
116+ ilg . Emit ( OpCodes . Throw ) ;
117+ }
118+ ilg . MarkLabel ( readValueLabel ) ;
119+
120+ // read the serialised type name
121+ var typeName = ilg . DeclareLocal ( typeof ( string ) ) ;
122+ ilg . Emit ( OpCodes . Ldloc , unpacker ) ;
123+ ilg . Emit ( OpCodes . Ldloca , typeName ) ;
124+ ilg . Emit ( OpCodes . Call , typeof ( Unpacker ) . GetMethod ( nameof ( Unpacker . TryReadString ) , new [ ] { typeof ( string ) . MakeByRefType ( ) } ) ) ;
125+
126+ var lbl1 = ilg . DefineLabel ( ) ;
127+ ilg . Emit ( OpCodes . Brtrue , lbl1 ) ;
128+ {
129+ ilg . Emit ( OpCodes . Ldstr , "Unable to read union type name for property \" {0}\" of type \" {1}\" " ) ;
130+ ilg . Emit ( OpCodes . Ldstr , name ) ;
131+ ilg . LoadType ( value . LocalType ) ;
132+ ilg . Emit ( OpCodes . Call , typeof ( string ) . GetMethod ( nameof ( string . Format ) , new [ ] { typeof ( string ) , typeof ( object ) , typeof ( object ) } ) ) ;
133+ ilg . LoadType ( targetType ) ;
134+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
135+ ilg . Emit ( OpCodes . Throw ) ;
136+ }
137+ ilg . MarkLabel ( lbl1 ) ;
138+
139+ // loop through types within the union, looking for a matching type name
140+ var doneLabel = ilg . DefineLabel ( ) ;
141+ var labelNextType = ilg . DefineLabel ( ) ;
142+ foreach ( var type in value . LocalType . GetGenericArguments ( ) )
143+ {
144+ var expectedTypeName = GetTypeName ( type ) ;
145+
146+ ilg . Emit ( OpCodes . Ldloc , typeName ) ;
147+ ilg . Emit ( OpCodes . Ldstr , expectedTypeName ) ;
148+ ilg . Emit ( OpCodes . Call , typeof ( string ) . GetMethod ( nameof ( string . Equals ) , BindingFlags . Static | BindingFlags . Public , null , new [ ] { typeof ( string ) , typeof ( string ) } , null ) ) ;
149+
150+ // continue if this type doesn't match the union's values
151+ ilg . Emit ( OpCodes . Brfalse , labelNextType ) ;
152+
153+ // we have a match
154+ // read the value
155+ var readValue = ilg . DeclareLocal ( type ) ;
156+ if ( ! context . TryDeserialise ( ilg , name , targetType , readValue , unpacker , contextLocal , unexpectedFieldBehaviour ) )
157+ throw new Exception ( $ "Unable to deserialise values of type { type } from MsgPack data.") ;
158+
159+ // create the union
160+ ilg . Emit ( OpCodes . Ldloc , readValue ) ;
161+ ilg . Emit ( OpCodes . Call , value . LocalType . GetMethod ( nameof ( Union < int , int > . Create ) , new [ ] { type } ) ) ;
162+
163+ // store it in the result value
164+ ilg . Emit ( OpCodes . Stloc , value ) ;
165+
166+ // exit the loop
167+ ilg . Emit ( OpCodes . Br , doneLabel ) ;
168+
169+ ilg . MarkLabel ( labelNextType ) ;
170+ labelNextType = ilg . DefineLabel ( ) ;
171+ }
172+
173+ ilg . MarkLabel ( labelNextType ) ;
174+
175+ // TODO include received type name in error message and some more general info
176+ ilg . Emit ( OpCodes . Ldstr , "No match on union type" ) ;
177+ ilg . Emit ( OpCodes . Newobj , typeof ( Exception ) . GetConstructor ( new [ ] { typeof ( string ) } ) ) ;
178+ ilg . Emit ( OpCodes . Throw ) ;
179+
180+ ilg . MarkLabel ( doneLabel ) ;
181+ }
182+
183+ public static string GetTypeName ( Type type )
184+ {
185+ if ( ! type . IsGenericType )
186+ return type . Namespace == nameof ( System ) ? type . Name : type . FullName ;
187+
188+ var arguments = type . GetGenericArguments ( ) ;
189+ if ( arguments . Length == 1 && type . GetGenericTypeDefinition ( ) == typeof ( IReadOnlyList < > ) )
190+ return $ "[{ GetTypeName ( arguments [ 0 ] ) } ]";
191+ if ( arguments . Length == 2 && type . GetGenericTypeDefinition ( ) == typeof ( IReadOnlyDictionary < , > ) )
192+ return $ "({ GetTypeName ( arguments [ 0 ] ) } =>{ GetTypeName ( arguments [ 1 ] ) } )";
193+
194+ var baseName = type . FullName . StartsWith ( "Dasher.Union`" )
195+ ? "Union"
196+ : type . FullName . Substring ( 0 , type . FullName . IndexOf ( '`' ) ) ;
197+
198+ return $ "{ baseName } <{ string . Join ( "," , arguments . Select ( GetTypeName ) ) } >";
199+ }
200+ }
201+ }
0 commit comments