diff --git a/Orm/Xtensive.Orm.Tests.Core/Tuples/NullableTokenCollisionTest.cs b/Orm/Xtensive.Orm.Tests.Core/Tuples/NullableTokenCollisionTest.cs new file mode 100644 index 000000000..7b972069f --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Core/Tuples/NullableTokenCollisionTest.cs @@ -0,0 +1,50 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using NUnit.Framework; + +namespace Xtensive.Tuples; + +[TestFixture] +public class NullableTokenCollisionTest +{ + [Test] + public void EnumWithNullableMetadataTokenIsNotTreatedAsNullable() + { + var nullableToken = typeof(Nullable<>).MetadataToken; + var enumType = EmitEnumWithMetadataToken(nullableToken); + + Assert.That(enumType.IsEnum, Is.True); + Assert.That(enumType.MetadataToken, Is.EqualTo(nullableToken)); + Assert.That(enumType.Module, Is.Not.EqualTo(typeof(Nullable<>).Module)); + Assert.DoesNotThrow(() =>TupleDescriptor.Create(new[] { enumType})); + } + + [Test] + public void GenuineNullableEnumStillResolves() + { + var nullableToken = typeof(Nullable<>).MetadataToken; + var enumType = EmitEnumWithMetadataToken(nullableToken); + var nullableEnum = typeof(Nullable<>).MakeGenericType(enumType); + + Assert.DoesNotThrow(() => TupleDescriptor.Create(new[] { nullableEnum })); + } + + private static Type EmitEnumWithMetadataToken(int targetToken) + { + var rowId = targetToken & 0X00FFFFFF; + var assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName("NullableTokenCollisionAsm"), AssemblyBuilderAccess.Run); + var module = assembly.DefineDynamicModule("NullableTokenCollisionModule"); + + //Starting at 2 because module is at 1 and then we add fake test until we reach the desired value + for (var i = 2; i < rowId; i++) { + module.DefineType("Filler" + i, TypeAttributes.Public).CreateType(); + } + + var enumBuilder = module.DefineEnum("CollidingStatus", TypeAttributes.Public, typeof(int)); + enumBuilder.DefineLiteral("FirstOption", 0); + enumBuilder.DefineLiteral("SecondOption", 1); + return enumBuilder.CreateType(); + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs index 221a9e188..ec2c71c2a 100644 --- a/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs +++ b/Orm/Xtensive.Orm/Tuples/Packed/TupleLayout.cs @@ -5,6 +5,7 @@ // Created: 2012.12.29 using System; +using System.Reflection; using System.Runtime.CompilerServices; using Xtensive.Reflection; @@ -54,10 +55,11 @@ private static class ValueFieldAccessorResolver private static readonly ValueFieldAccessor DateTimeOffsetAccessor = new DateTimeOffsetFieldAccessor(); private static readonly int NullableTypeMetadataToken = WellKnownTypes.NullableOfT.MetadataToken; + private static readonly Module NullableTypeModule = WellKnownTypes.NullableOfT.Module; public static ValueFieldAccessor GetValue(Type probeType) { - return (probeType.MetadataToken ^ NullableTypeMetadataToken) == 0 + return probeType.MetadataToken == NullableTypeMetadataToken && probeType.Module == NullableTypeModule ? (ResolveByNullableType(probeType) ?? TryResolveNullableEnum(probeType)) : (ResolveByType(probeType) ?? TryResolveEnum(probeType)); }