2323using System . Diagnostics . CodeAnalysis ;
2424using System . Linq ;
2525using System . Reflection ;
26+ using System . Reflection . Metadata . Ecma335 ;
2627using System . Runtime . CompilerServices ;
2728
2829using ICSharpCode . Decompiler . CSharp . Resolver ;
@@ -1293,26 +1294,47 @@ bool IsFlagsEnum(ITypeDefinition type)
12931294 return type . HasAttribute ( KnownAttribute . Flags ) ;
12941295 }
12951296
1296- Expression ConvertEnumValue ( IType type , long val )
1297+ /// <summary>
1298+ /// Converts a numeric enum value into its enum member representation, if possible.
1299+ /// Uses a series of enum members concatenated by <c>|</c>, if necessary.
1300+ /// Returns <c>(EnumType)value</c>, if it fails.
1301+ /// <para>
1302+ /// If <paramref name="declaringEnumMember"/> is set to a non-<see langword="null"/> value,
1303+ /// unqualified references are used and self references are avoided. Also, casts to EnumType are dropped.
1304+ /// </para>
1305+ /// </summary>
1306+ internal Expression ConvertEnumValue ( IType type , long val , IField ? declaringEnumMember = null )
12971307 {
12981308 ITypeDefinition enumDefinition = type . GetDefinition ( ) ! ;
12991309 TypeCode enumBaseTypeCode = ReflectionHelper . GetTypeCode ( enumDefinition . EnumUnderlyingType ) ;
1310+ bool isFlags = IsFlagsEnum ( enumDefinition ) ;
13001311 var fields = enumDefinition . Fields
13011312 . Select ( PrepareConstant )
13021313 . Where ( f => f . field != null )
13031314 . ToArray ( ) ;
1304- foreach ( var ( value , field ) in fields )
1315+ int declaringTokenRowNumber = declaringEnumMember == null ? int . MaxValue : MetadataTokens . GetRowNumber ( declaringEnumMember . MetadataToken ) ;
1316+ foreach ( var ( value , field , weight ) in fields )
13051317 {
1306- if ( value == val )
1318+ // In a [Flags] enum declaration, only reference single-bit members directly:
1319+ // combined values are built from their flag components below (so that
1320+ // e.g. All = Item1 | Item2 | Item3), and zero members stay numeric, because
1321+ // mask-style enums routinely contain several unrelated zero members
1322+ // (e.g. MethodAttributes.PrivateScope/ReuseSlot).
1323+ if ( value == val && ( declaringEnumMember == null || ! isFlags || weight == 1 ) )
13071324 {
1308- var mre = new MemberReferenceExpression ( new TypeReferenceExpression ( ConvertType ( type ) ) , field . Name ) ;
1309- if ( AddResolveResultAnnotations )
1310- mre . AddAnnotation ( new MemberResolveResult ( mre . Target . GetResolveResult ( ) , field ) ) ;
1311- return mre ;
1325+ if ( field == declaringEnumMember || ( declaringTokenRowNumber < MetadataTokens . GetRowNumber ( field . MetadataToken ) ) )
1326+ {
1327+ return ConvertConstantValue ( enumDefinition . EnumUnderlyingType ! , CSharpPrimitiveCast . Cast ( enumBaseTypeCode , val , false ) ) ;
1328+ }
1329+ return MakeEnumMemberReference ( field ) ;
13121330 }
13131331 }
1314- if ( IsFlagsEnum ( enumDefinition ) )
1332+ if ( isFlags )
13151333 {
1334+ // The complement of a byte- or ushort-based enum member is computed in int and
1335+ // therefore negative, which an enum member initializer cannot implicitly convert
1336+ // back to the underlying type -- the ~X form would not compile there.
1337+ bool complementCompiles = declaringEnumMember == null || enumBaseTypeCode is not ( TypeCode . Byte or TypeCode . UInt16 ) ;
13161338 long enumValue = val ;
13171339 Expression ? expr = null ;
13181340 long negatedEnumValue = ~ val ;
@@ -1333,24 +1355,27 @@ Expression ConvertEnumValue(IType type, long val)
13331355 break ;
13341356 }
13351357 Expression ? negatedExpr = null ;
1336- foreach ( var ( fieldValue , field ) in fields . OrderByDescending ( f => CalculateHammingWeight ( unchecked ( ( ulong ) f . value ) ) ) )
1358+ foreach ( var ( fieldValue , field , weight ) in fields . OrderByDescending ( f => f . weight ) )
13371359 {
1338- if ( fieldValue == 0 )
1360+ if ( fieldValue == 0 || field == declaringEnumMember )
13391361 continue ; // skip None enum value
13401362
1363+ if ( declaringTokenRowNumber < MetadataTokens . GetRowNumber ( field . MetadataToken ) )
1364+ continue ;
1365+
13411366 if ( ( fieldValue & enumValue ) == fieldValue )
13421367 {
1343- var fieldExpression = new MemberReferenceExpression ( new TypeReferenceExpression ( ConvertType ( type ) ) , field . Name ) ;
1368+ var fieldExpression = MakeEnumMemberReference ( field ) ;
13441369 if ( expr == null )
13451370 expr = fieldExpression ;
13461371 else
13471372 expr = new BinaryOperatorExpression ( expr , BinaryOperatorType . BitwiseOr , fieldExpression ) ;
13481373
13491374 enumValue &= ~ fieldValue ;
13501375 }
1351- if ( ( fieldValue & negatedEnumValue ) == fieldValue )
1376+ if ( complementCompiles && ( fieldValue & negatedEnumValue ) == fieldValue )
13521377 {
1353- var fieldExpression = new MemberReferenceExpression ( new TypeReferenceExpression ( ConvertType ( type ) ) , field . Name ) ;
1378+ var fieldExpression = MakeEnumMemberReference ( field ) ;
13541379 if ( negatedExpr == null )
13551380 negatedExpr = fieldExpression ;
13561381 else
@@ -1359,28 +1384,43 @@ Expression ConvertEnumValue(IType type, long val)
13591384 negatedEnumValue &= ~ fieldValue ;
13601385 }
13611386 }
1362- if ( enumValue == 0 && expr != null )
1387+ // A multi-bit value that lies entirely within a larger, previously declared member
1388+ // is usually a field encoding inside that mask (e.g. TypeAttributes.NestedPrivate
1389+ // within VisibilityMask), not a union of independent flags; keep it numeric.
1390+ bool isEncodedInEarlierMask = declaringEnumMember != null && fields . Any (
1391+ f => f . field != declaringEnumMember
1392+ && MetadataTokens . GetRowNumber ( f . field . MetadataToken ) < declaringTokenRowNumber
1393+ && ( f . value & val ) == val && f . value != val ) ;
1394+ if ( enumValue == 0 && expr != null && ! isEncodedInEarlierMask )
13631395 {
13641396 if ( ! ( negatedEnumValue == 0 && negatedExpr != null && negatedExpr . Descendants . Count ( ) < expr . Descendants . Count ( ) ) )
13651397 {
13661398 return expr ;
13671399 }
13681400 }
1369- if ( negatedEnumValue == 0 && negatedExpr != null )
1401+ if ( complementCompiles && negatedEnumValue == 0 && negatedExpr != null )
13701402 {
13711403 return new UnaryOperatorExpression ( UnaryOperatorType . BitNot , negatedExpr ) ;
13721404 }
13731405 }
1374- return new CastExpression ( ConvertType ( type ) , new PrimitiveExpression ( CSharpPrimitiveCast . Cast ( enumBaseTypeCode , val , false ) ) ) ;
13751406
1376- ( long value , IField field ) PrepareConstant ( IField field )
1407+ var numericExpression = ConvertConstantValue ( enumDefinition . EnumUnderlyingType ! , CSharpPrimitiveCast . Cast ( enumBaseTypeCode , val , false ) ) ;
1408+ if ( declaringEnumMember != null )
1409+ {
1410+ return numericExpression ;
1411+ }
1412+ return new CastExpression ( ConvertType ( type ) , numericExpression ) ;
1413+
1414+ ( long value , IField field , int weight ) PrepareConstant ( IField field )
13771415 {
13781416 if ( ! field . IsConst )
1379- return ( - 1 , null ! ) ;
1417+ return default ;
13801418 object ? constantValue = field . GetConstantValue ( ) ;
13811419 if ( constantValue == null )
1382- return ( - 1 , null ! ) ;
1383- return ( ( long ) CSharpPrimitiveCast . Cast ( TypeCode . Int64 , constantValue , checkForOverflow : false ) , field ) ;
1420+ return default ;
1421+ var value = ( long ) CSharpPrimitiveCast . Cast ( TypeCode . Int64 , constantValue , checkForOverflow : false ) ;
1422+ var weight = CalculateHammingWeight ( unchecked ( ( ulong ) value ) ) ;
1423+ return ( value , field , weight ) ;
13841424 }
13851425
13861426 // see https://en.wikipedia.org/wiki/Hamming_weight
@@ -1395,6 +1435,24 @@ int CalculateHammingWeight(ulong value)
13951435 x = ( x + ( x >> 4 ) ) & m4 ; //put count of each 8 bits into those 8 bits
13961436 return unchecked ( ( int ) ( ( x * h01 ) >> 56 ) ) ; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
13971437 }
1438+
1439+ Expression MakeEnumMemberReference ( IField field )
1440+ {
1441+ if ( declaringEnumMember == null )
1442+ {
1443+ var mre = new MemberReferenceExpression ( new TypeReferenceExpression ( ConvertType ( type ) ) , field . Name ) ;
1444+ if ( AddResolveResultAnnotations )
1445+ mre . AddAnnotation ( new MemberResolveResult ( mre . Target . GetResolveResult ( ) , field ) ) ;
1446+ return mre ;
1447+ }
1448+ else
1449+ {
1450+ var ie = new IdentifierExpression ( field . Name ) ;
1451+ if ( AddResolveResultAnnotations )
1452+ ie . AddAnnotation ( new MemberResolveResult ( null , field ) ) ;
1453+ return ie ;
1454+ }
1455+ }
13981456 }
13991457
14001458 static bool IsValidFraction ( long num , long den )
0 commit comments