14
14
using System . Globalization ;
15
15
using System . Linq ;
16
16
using System . Reflection ;
17
+ using System . Reflection . Emit ;
17
18
using System . Threading . Tasks ;
18
19
using TS3AudioBot . CommandSystem . CommandResults ;
19
20
using TS3AudioBot . Dependency ;
22
23
using TS3AudioBot . Web . Api ;
23
24
using TSLib . Helper ;
24
25
using static TS3AudioBot . CommandSystem . CommandSystemTypes ;
26
+ using TryFromFn = System . Func < object ? , object ? > ;
25
27
26
28
namespace TS3AudioBot . CommandSystem . Commands
27
29
{
@@ -158,7 +160,7 @@ public FunctionCommand(Func<string, string> command) : this(command.Method, comm
158
160
if ( arg . Kind == ParamKind . NormalTailString && argResultP is TailString tailString )
159
161
parameters [ p ] = tailString . Tail ;
160
162
else
161
- parameters [ p ] = ConvertParam ( argResultP , argType , filterLazy ) ;
163
+ parameters [ p ] = ConvertParam ( argResultP , argType , arg , filterLazy ) ;
162
164
163
165
takenArguments ++ ;
164
166
break ;
@@ -171,7 +173,7 @@ public FunctionCommand(Func<string, string> command) : this(command.Method, comm
171
173
for ( int i = 0 ; i < args . Length ; i ++ , takenArguments ++ )
172
174
{
173
175
var argResultA = await arguments [ takenArguments ] . Execute ( info , Array . Empty < ICommand > ( ) ) ;
174
- var convResult = ConvertParam ( argResultA , typeArr , filterLazy ) ;
176
+ var convResult = ConvertParam ( argResultA , typeArr , arg , filterLazy ) ;
175
177
args . SetValue ( convResult , i ) ;
176
178
}
177
179
@@ -222,14 +224,63 @@ private static ParamInfo[] PrecomputeTypes(ParameterInfo[] parameterInfos)
222
224
kind = ParamKind . NormalArray ;
223
225
else if ( arg . IsEnum
224
226
|| BasicTypes . Contains ( arg )
225
- || BasicTypes . Contains ( UnwrapParamType ( arg ) ) )
227
+ || BasicTypes . Contains ( UnwrapParamType ( arg ) )
228
+ || AdvancedTypes . Contains ( arg )
229
+ || AdvancedTypes . Contains ( UnwrapParamType ( arg ) ) )
226
230
kind = ParamKind . NormalParam ;
227
231
// TODO How to distinguish between special type and dependency?
228
- else if ( AdvancedTypes . Contains ( arg ) )
229
- kind = ParamKind . NormalParam ;
230
232
else
231
233
kind = ParamKind . Dependency ;
232
234
235
+ TryFromFn ? tryFrom = null ;
236
+ if ( kind == ParamKind . NormalParam || kind == ParamKind . NormalArray )
237
+ {
238
+ var finalType = arg ;
239
+ if ( kind == ParamKind . NormalArray )
240
+ finalType = finalType . GetElementType ( ) ?? throw new InvalidOperationException ( "Not an array?" ) ;
241
+ finalType = UnwrapParamType ( finalType ) ;
242
+ var method = finalType . GetMethod ( "TryFrom" , BindingFlags . Public | BindingFlags . Static ) ;
243
+ if ( method != null )
244
+ {
245
+ if ( method . ReturnType == typeof ( object ) )
246
+ {
247
+ // return directly: (object) -> object
248
+ tryFrom = ( TryFromFn ) method . CreateDelegate ( typeof ( TryFromFn ) ) ;
249
+ }
250
+ else if ( method . ReturnType . IsClass )
251
+ {
252
+ // have: [object]->class
253
+ // can be casted with covariance to [object]->object
254
+ tryFrom = ( TryFromFn ) method . CreateDelegate ( typeof ( Func < , > ) . MakeGenericType ( typeof ( object ) , method . ReturnType ) ) ;
255
+ }
256
+ else if ( method . ReturnType . IsValueType )
257
+ {
258
+ static TryFromFn ? TryCreateWith ( MethodInfo method , Type retType )
259
+ {
260
+ if ( method . ReturnType != retType ) return null ;
261
+ try
262
+ {
263
+ var tryFromToObjectWrapper = new DynamicMethod (
264
+ "TryFromToObjectWrapper" , typeof ( object ) , new [ ] { typeof ( object ) } , typeof ( FunctionCommand ) . Module ) ;
265
+ var il = tryFromToObjectWrapper . GetILGenerator ( ) ;
266
+ il . Emit ( OpCodes . Ldarg_0 ) ;
267
+ il . Emit ( OpCodes . Call , method ) ;
268
+ il . Emit ( OpCodes . Box , retType ) ;
269
+ il . Emit ( OpCodes . Ret ) ;
270
+ return ( TryFromFn ) tryFromToObjectWrapper . CreateDelegate ( typeof ( TryFromFn ) ) ;
271
+ }
272
+ catch { return null ; }
273
+ }
274
+ // have: [object]->value
275
+ // box with (object)([object]->value)
276
+ tryFrom ??= TryCreateWith ( method , method . ReturnType ) ;
277
+ // have: [object]->value?
278
+ // box with (object)([object]->value?)
279
+ tryFrom ??= TryCreateWith ( method , typeof ( Nullable < > ) . MakeGenericType ( method . ReturnType ) ) ;
280
+ }
281
+ }
282
+ }
283
+
233
284
if ( kind . IsNormal ( ) )
234
285
{
235
286
// If we have the last normal parameter, check if it fits the criteria
@@ -244,7 +295,8 @@ private static ParamInfo[] PrecomputeTypes(ParameterInfo[] parameterInfos)
244
295
precomputed [ i ] = new ParamInfo (
245
296
parameterInfo ,
246
297
kind ,
247
- parameterInfo . IsOptional || parameterInfo . GetCustomAttribute < ParamArrayAttribute > ( ) != null ) ;
298
+ parameterInfo . IsOptional || parameterInfo . GetCustomAttribute < ParamArrayAttribute > ( ) != null ,
299
+ tryFrom ) ;
248
300
}
249
301
250
302
return precomputed ;
@@ -307,7 +359,7 @@ public static CommandException ThrowAtLeastNArguments(int count)
307
359
}
308
360
309
361
[ return : NotNullIfNotNull ( "value" ) ]
310
- public static object ? ConvertParam ( object ? value , Type targetType , Lazy < Algorithm . IFilter > filter )
362
+ public static object ? ConvertParam ( object ? value , Type targetType , ParamInfo param , Lazy < Algorithm . IFilter > filter )
311
363
{
312
364
if ( value is null )
313
365
return null ;
@@ -322,6 +374,15 @@ public static CommandException ThrowAtLeastNArguments(int count)
322
374
if ( targetType == valueType || targetType . IsAssignableFrom ( valueType ) )
323
375
return value ;
324
376
377
+ if ( param . TryFrom != null )
378
+ {
379
+ var tryResult = param . TryFrom ( value ) ;
380
+ if ( tryResult != null )
381
+ {
382
+ return tryResult ;
383
+ }
384
+ }
385
+
325
386
if ( targetType . IsEnum )
326
387
{
327
388
var strValue = value . ToString ( ) ?? throw new ArgumentNullException ( nameof ( value ) ) ;
@@ -374,19 +435,21 @@ public enum ParamKind
374
435
}
375
436
376
437
[ DebuggerDisplay ( "{Kind} {Name,nq}{Optional ? \" ?\" : \" \" ,nq} ({Type.Name,nq})" ) ]
377
- public readonly struct ParamInfo
438
+ public class ParamInfo
378
439
{
379
440
public ParamKind Kind { get ; }
380
441
public bool Optional { get ; }
381
442
public ParameterInfo Param { get ; }
382
- public readonly Type Type => Param . ParameterType ;
383
- public readonly string Name => Param . Name ?? "<no name>" ;
443
+ public TryFromFn ? TryFrom { get ; }
444
+ public Type Type => Param . ParameterType ;
445
+ public string Name => Param . Name ?? "<no name>" ;
384
446
385
- public ParamInfo ( ParameterInfo param , ParamKind kind , bool optional )
447
+ public ParamInfo ( ParameterInfo param , ParamKind kind , bool optional , TryFromFn ? tryFrom )
386
448
{
387
449
Param = param ;
388
450
Kind = kind ;
389
451
Optional = optional ;
452
+ TryFrom = tryFrom ;
390
453
}
391
454
}
392
455
0 commit comments