Skip to content

Commit 592d418

Browse files
committed
Add null entry skipping for map based serializer. #136
This commit add new option OmitNullEntry. For async support and dynamics, PackHelper's parameters are added with backward compatibility to null checking. This commit also fix a bug that serializer contains extra delegate fields and a member names field.
1 parent 701cb33 commit 592d418

24 files changed

Lines changed: 1397 additions & 128 deletions

src/MsgPack/Serialization/AbstractSerializers/ActionType.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// MessagePack for CLI
44
//
5-
// Copyright (C) 2015 FUJIWARA, Yusuke
5+
// Copyright (C) 2015-2016 FUJIWARA, Yusuke
66
//
77
// Licensed under the Apache License, Version 2.0 (the "License");
88
// you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ internal enum ActionType
3131
PackToMap,
3232
UnpackFromArray,
3333
UnpackFromMap,
34-
UnpackTo
34+
UnpackTo,
35+
IsNull
3536
}
3637
}

src/MsgPack/Serialization/AbstractSerializers/FieldName.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal static class FieldName
2626
{
2727
public const string PackOperationList = "_packOperationList";
2828
public const string PackOperationTable = "_packOperationTable";
29+
public const string NullCheckersTable = "_nullCheckersTable";
2930
public const string UnpackOperationList = "_unpackOperationList";
3031
public const string UnpackOperationTable = "_unpackOperationTable";
3132
public const string UnpackTo = "_unpackTo";

src/MsgPack/Serialization/AbstractSerializers/MethodDefinition.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,13 @@ private MethodInfo ResolveRuntimeMethodCore( bool throws )
128128
public readonly TypeDefinition DeclaringType;
129129
public readonly TypeDefinition ReturnType;
130130
public readonly TypeDefinition[] ParameterTypes;
131+
public readonly bool IsStatic;
131132

132-
public MethodDefinition( string name, TypeDefinition[] genericArguments, TypeDefinition declaringType, TypeDefinition returnType, params TypeDefinition[] parameterTypes )
133+
public MethodDefinition( string name, TypeDefinition[] genericArguments, TypeDefinition declaringType, bool isStatic, TypeDefinition returnType, params TypeDefinition[] parameterTypes )
133134
{
134135
this.MethodName = name;
135136
this.DeclaringType = declaringType;
137+
this.IsStatic = isStatic;
136138
this._runtimeMethod = null;
137139
this._genericArguments = genericArguments;
138140
this.ReturnType = returnType;
@@ -152,6 +154,7 @@ public MethodDefinition( MethodInfo runtimeMethod, Type @interface, IEnumerable<
152154
#endif // DEBUG
153155
this.MethodName = runtimeMethod.Name;
154156
this.DeclaringType = runtimeMethod.DeclaringType;
157+
this.IsStatic = runtimeMethod.IsStatic;
155158
this._runtimeMethod = runtimeMethod;
156159
this.ReturnType = runtimeMethod.ReturnType;
157160
this.ParameterTypes = parameterTypes.ToArray();

src/MsgPack/Serialization/AbstractSerializers/SerializerBuilder`2.Collection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ private IEnumerable<TConstruct> EmitCollectionUnpackFromStatements( TContext con
321321
this.ExtractPrivateMethod(
322322
context,
323323
AdjustName( MethodName.UnpackCollectionItem, isAsync ),
324+
false, // isStatic
324325
#if FEATURE_TAP
325326
isAsync ? typeof( Task ) :
326327
#endif // FEATURE_TAP
@@ -339,6 +340,7 @@ private IEnumerable<TConstruct> EmitCollectionUnpackFromStatements( TContext con
339340
this.ExtractPrivateMethod(
340341
context,
341342
MethodName.AppendUnpackedItem,
343+
false, // isStatic
342344
typeof( void ),
343345
() => this.EmitAppendCollectionItem(
344346
context,

src/MsgPack/Serialization/AbstractSerializers/SerializerBuilder`2.CommonConstructs.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -713,33 +713,48 @@ protected TConstruct EmitInvokeMethodExpression(
713713
/// </summary>
714714
/// <param name="context">The generation context.</param>
715715
/// <param name="name">The name of the private method.</param>
716+
/// <param name="isStatic"><c>true</c> for static method.</param>
716717
/// <param name="returnType">The type of return value.</param>
717718
/// <param name="bodyFactory">The delegate to the factory which returns body of the private method.</param>
718719
/// <param name="parameters">The parameters of the private method.</param>
719720
/// <returns>
720721
/// The generated construct which represents delegate creation instruction to call the private method.
721722
/// Note that returned value remains in context.
722723
/// </returns>
723-
private TConstruct ExtractPrivateMethod( TContext context, string name, TypeDefinition returnType, Func<TConstruct> bodyFactory, params TConstruct[] parameters )
724+
private TConstruct ExtractPrivateMethod( TContext context, string name, bool isStatic, TypeDefinition returnType, Func<TConstruct> bodyFactory, params TConstruct[] parameters )
725+
{
726+
return this.EmitGetPrivateMethodDelegateExpression( context, this.DefinePrivateMethod( context, name, isStatic, returnType, bodyFactory, parameters ) );
727+
}
728+
729+
/// <summary>
730+
/// Emits specified body as individual private method and returns its metadata.
731+
/// </summary>
732+
/// <param name="context">The generation context.</param>
733+
/// <param name="name">The name of the private method.</param>
734+
/// <param name="isStatic"><c>true</c> for static method.</param>
735+
/// <param name="returnType">The type of return value.</param>
736+
/// <param name="bodyFactory">The delegate to the factory which returns body of the private method.</param>
737+
/// <param name="parameters">The parameters of the private method.</param>
738+
/// <returns>
739+
/// The generated metadata of the private method.
740+
/// </returns>
741+
private MethodDefinition DefinePrivateMethod( TContext context, string name, bool isStatic, TypeDefinition returnType, Func<TConstruct> bodyFactory, params TConstruct[] parameters )
724742
{
725-
MethodDefinition method;
726743
if ( context.IsDeclaredMethod( name ) )
727744
{
728-
method = context.GetDeclaredMethod( name );
745+
return context.GetDeclaredMethod( name );
729746
}
730747
else
731748
{
732749
context.BeginPrivateMethod(
733750
name,
734-
false,
751+
isStatic,
735752
returnType,
736753
parameters
737-
);
754+
);
738755

739-
method = context.EndPrivateMethod( name, bodyFactory() );
756+
return context.EndPrivateMethod( name, bodyFactory() );
740757
}
741-
742-
return this.EmitGetPrivateMethodDelegateExpression( context, method );
743758
}
744759

745760
protected virtual TConstruct EmitGetPrivateMethodDelegateExpression( TContext context, MethodDefinition method )
@@ -1368,6 +1383,7 @@ bool isAsync
13681383
? new [] { unpackingContext.ContextType, Nullable.GetUnderlyingType( memberType ) }
13691384
: new [] { unpackingContext.ContextType, memberType },
13701385
typeof( UnpackHelpers ),// declaring type
1386+
true, // isStatic
13711387
unpackingContext.ContextType, // return type
13721388
TypeDefinition.ManagedReference( unpackHelperParameterType )
13731389
);

src/MsgPack/Serialization/AbstractSerializers/SerializerBuilder`2.Object.cs

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ private IEnumerable<TConstruct> BuildObjectPackToCore( TContext context, IList<S
194194
isAsync ? new[] { this.ReferCancellationToken( context, 3 ) } :
195195
#endif // FEATURE_TAP
196196
NoConstructs;
197+
var nullCheckParameters = new[] { context.NullCheckTarget };
197198
var methodForNull =
198199
#if FEATURE_TAP
199200
isAsync ? Metadata._Packer.PackNullAsync :
@@ -217,9 +218,10 @@ private IEnumerable<TConstruct> BuildObjectPackToCore( TContext context, IList<S
217218
}
218219

219220
// missing member, always nil
220-
this.ExtractPrivateMethod(
221+
this.DefinePrivateMethod(
221222
context,
222223
AdjustName( MethodName.PackMemberPlaceHolder, isAsync ),
224+
false, // isStatic
223225
#if FEATURE_TAP
224226
isAsync ? typeof( Task ) :
225227
#endif // FEATURE_TAP
@@ -232,9 +234,12 @@ private IEnumerable<TConstruct> BuildObjectPackToCore( TContext context, IList<S
232234
}
233235
else
234236
{
235-
this.ExtractPrivateMethod(
237+
var itemType = entries[ count ].Member.GetMemberValueType();
238+
// PackValue
239+
this.DefinePrivateMethod(
236240
context,
237241
GetPackValueMethodName( entries[ i ], isAsync ),
242+
false, // isStatic
238243
#if FEATURE_TAP
239244
isAsync ? typeof( Task ) :
240245
#endif // FEATURE_TAP
@@ -245,7 +250,7 @@ private IEnumerable<TConstruct> BuildObjectPackToCore( TContext context, IList<S
245250
this.EmitPackItemStatements(
246251
context,
247252
context.Packer,
248-
entries[ count ].Member.GetMemberValueType(),
253+
itemType,
249254
entries[ count ].Contract.NilImplication,
250255
entries[ count ].Member.ToString(),
251256
this.EmitGetMemberValueExpression( context, context.PackToTarget, entries[ count ].Member ),
@@ -256,6 +261,32 @@ entries[ count ].Member.ToString(),
256261
),
257262
parameters
258263
);
264+
265+
if ( !SerializerDebugging.UseLegacyNullMapEntryHandling
266+
&& ( !itemType.GetIsValueType() || Nullable.GetUnderlyingType( itemType ) != null ) )
267+
{
268+
var nullCheckTarget = this.EmitGetMemberValueExpression( context, context.NullCheckTarget, entries[ count ].Member );
269+
// Trying uses nullCheckTarget.ContextType because it may be Object when using reflection.
270+
var nullCheckTargetType = nullCheckTarget.ContextType.TryGetRuntimeType() ?? itemType;
271+
// CheckNull
272+
this.DefinePrivateMethod(
273+
context,
274+
GetCheckNullMethodName( entries[ i ] ),
275+
false, // isStatic
276+
typeof( bool ),
277+
() =>
278+
nullCheckTargetType.GetIsValueType() // Is Nullable<T> ?
279+
? this.EmitSequentialStatements(
280+
context,
281+
typeof( bool ),
282+
this.EmitHasValueCore( context, nullCheckTarget, nullCheckTargetType )
283+
) : this.EmitRetrunStatement(
284+
context,
285+
this.EmitEqualsExpression( context, nullCheckTarget, this.MakeNullLiteral( context, nullCheckTargetType ) )
286+
),
287+
nullCheckParameters
288+
);
289+
}
259290
}
260291
}
261292

@@ -276,6 +307,16 @@ entries[ count ].Member.ToString(),
276307
}
277308
};
278309

310+
if ( method == SerializationMethod.Map && !SerializerDebugging.UseLegacyNullMapEntryHandling )
311+
{
312+
packHelperArguments.Add(
313+
"SerializationContext",
314+
this.EmitGetPropertyExpression( context, this.EmitThisReferenceExpression( context ), Metadata._MessagePackSerializer.OwnerContext )
315+
);
316+
// isAsync is always false to prevent _nullCheckersTableAsync creation.
317+
packHelperArguments.Add( "NullCheckers", this.EmitGetActionsExpression( context, ActionType.IsNull, isAsync: false ) );
318+
}
319+
279320
#if FEATURE_TAP
280321
if ( isAsync )
281322
{
@@ -306,6 +347,7 @@ entries[ count ].Member.ToString(),
306347
packHelperMethodName,
307348
new TypeDefinition[] { this.TargetType },
308349
typeof( PackHelpers ),
350+
true, // isStatic
309351
#if FEATURE_TAP
310352
isAsync ? typeof( Task ) :
311353
#endif // FEATURE_TAP
@@ -365,6 +407,22 @@ entries[ count ].Member.ToString(),
365407
);
366408
}
367409

410+
private IEnumerable<TConstruct> EmitHasValueCore( TContext context, TConstruct nullCheckTarget, Type itemType )
411+
{
412+
var nullable = this.DeclareLocal( context, itemType, "nullable" );
413+
yield return nullable;
414+
yield return this.EmitStoreVariableStatement( context, nullable, nullCheckTarget );
415+
yield return
416+
this.EmitRetrunStatement(
417+
context,
418+
this.EmitEqualsExpression(
419+
context,
420+
this.EmitGetPropertyExpression( context, nullable, itemType.GetProperty( "HasValue" ) ),
421+
this.MakeBooleanLiteral( context, false )
422+
)
423+
);
424+
}
425+
368426
#endregion -- IPackable --
369427

370428
#region -- Pack Operation Initialization --
@@ -510,6 +568,64 @@ knownActions[ i ].Value
510568
);
511569
}
512570

571+
protected internal TConstruct EmitPackNullCheckerTableInitialization( TContext context, SerializationTarget targetInfo )
572+
{
573+
var listType = typeof( Dictionary<,> ).MakeGenericType( typeof( string ), typeof( Func<,> ).MakeGenericType( this.TargetType, typeof( bool ) ) );
574+
return
575+
this.EmitSequentialStatements(
576+
context,
577+
listType,
578+
this.EmitPackNullCheckerTableInitializationCore(
579+
context,
580+
targetInfo,
581+
this.DeclareLocal( context, listType, "nullCheckerTable" )
582+
)
583+
);
584+
}
585+
586+
private IEnumerable<TConstruct> EmitPackNullCheckerTableInitializationCore( TContext context, SerializationTarget targetInfo, TConstruct actionCollection )
587+
{
588+
yield return actionCollection;
589+
590+
var knownActions = GetDeclaredKnownActions( context, targetInfo, m => GetCheckNullMethodName( m ) );
591+
592+
yield return
593+
this.EmitStoreVariableStatement(
594+
context,
595+
actionCollection,
596+
this.EmitCreateNewObjectExpression(
597+
context,
598+
actionCollection,
599+
new ConstructorDefinition( actionCollection.ContextType, typeof( int ) ),
600+
this.MakeInt32Literal( context, knownActions.Length )
601+
)
602+
);
603+
604+
for ( int i = 0; i < knownActions.Length; i++ )
605+
{
606+
yield return
607+
this.EmitSetIndexedProperty(
608+
context,
609+
actionCollection,
610+
actionCollection.ContextType,
611+
"Item",
612+
// Set key as transformed.
613+
this.MakeStringLiteral( context, context.SerializationContext.DictionarySerlaizationOptions.SafeKeyTransformer( knownActions[ i ].Key ) ),
614+
this.EmitNewPrivateMethodDelegateExpression(
615+
context,
616+
knownActions[ i ].Value
617+
)
618+
);
619+
}
620+
621+
yield return
622+
this.EmitFinishFieldInitializationStatement(
623+
context,
624+
FieldName.NullCheckersTable,
625+
actionCollection
626+
);
627+
}
628+
513629
private static string GetPackValueMethodName( SerializingMember member, bool isAsync )
514630
{
515631
return
@@ -521,6 +637,11 @@ private static string GetPackValueMethodName( SerializingMember member, bool isA
521637
);
522638
}
523639

640+
private static string GetCheckNullMethodName( SerializingMember member )
641+
{
642+
return "Is" + member.MemberName + "Null";
643+
}
644+
524645
#endregion -- Pack Operation Initialization --
525646

526647
#region -- IUnpackable --
@@ -775,6 +896,7 @@ targetInfo.Members[ count ].Member.GetMemberValueType(),
775896
MethodNamePrefix.SetUnpackedValueOf + targetInfo.Members[ count ].Member.Name,
776897
null,
777898
null,
899+
false, // isStatic
778900
typeof( void ),
779901
unpackingContext.VariableType,
780902
targetInfo.Members[ count ].Member.GetMemberValueType()
@@ -787,9 +909,10 @@ targetInfo.Members[ count ].Member.GetMemberValueType()
787909
);
788910
}
789911

790-
this.ExtractPrivateMethod(
912+
this.DefinePrivateMethod(
791913
context,
792914
GetUnpackValueMethodName( targetInfo.Members[ i ], isAsync ),
915+
false, // isStatic
793916
#if FEATURE_TAP
794917
isAsync ? typeof( Task ) :
795918
#endif // FEATURE_TAP
@@ -833,6 +956,7 @@ targetInfo.Members[ count ].Member.GetMemberValueType()
833956
AdjustName( MethodNamePrefix.UnpackFrom + method, isAsync ),
834957
new[] { unpackingContext.Type, this.TargetType },
835958
typeof( UnpackHelpers ),
959+
true, // isStatic
836960
this.TargetType,
837961
unpackHelperArguments.Select( a => a.ContextType ).ToArray()
838962
),
@@ -1099,6 +1223,7 @@ private MethodDefinition GetCreateObjectFromContextMethod( UnpackingContextInfo
10991223
MethodName.CreateObjectFromContext,
11001224
null,
11011225
null,
1226+
true, // isStatic
11021227
this.TargetType,
11031228
unpackingContext.Type
11041229
);
@@ -1414,6 +1539,7 @@ private TConstruct EmitNewPrivateMethodDelegateExpressionWithCreation( TContext
14141539
this.ExtractPrivateMethod(
14151540
context,
14161541
method.MethodName,
1542+
false, // isStatic
14171543
method.ReturnType,
14181544
bodyFactory,
14191545
privateMethodParameters
@@ -1436,6 +1562,22 @@ private static KeyValuePair<string, MethodDefinition>[] GetKnownActions( TContex
14361562

14371563
}
14381564

1565+
private static KeyValuePair<string, MethodDefinition>[] GetDeclaredKnownActions( TContext context, SerializationTarget targetInfo, Func<SerializingMember, string> nameFactory )
1566+
{
1567+
return
1568+
targetInfo.Members
1569+
.Where( m => m.MemberName != null )
1570+
.Select( m =>
1571+
new KeyValuePair<string, MethodDefinition>(
1572+
m.MemberName,
1573+
context.TryGetDeclaredMethod( nameFactory( m ) )
1574+
)
1575+
).Where( kv => kv.Value != null )
1576+
.ToArray();
1577+
1578+
}
1579+
14391580
#endregion -- Operation Helpers --
14401581
}
14411582
}
1583+

0 commit comments

Comments
 (0)