Refactor and tests for MS.CSharp's GetBestAccessibleType() (dotnet/corefx#26391)
authorJon Hanna <jon@hackcraft.net>
Fri, 9 Feb 2018 04:26:23 +0000 (04:26 +0000)
committerVladimir Sadov <vsadov@microsoft.com>
Fri, 9 Feb 2018 04:26:23 +0000 (20:26 -0800)
* Rearrange GetBestAccessibleType to avoid repeated tests & recursion.

Keep all code for AggregateType in the first branch for AggregateType,
etc.

# Conflicts:
# src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeManager.cs

* Use ContextForMemberLookup instead of BindingContext for best accessible

The BindingContext is only used for this property, which it hits
repeatedly, so just pass it in instead.

# Conflicts:
# src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeManager.cs

* Don't use intermediate variables for out parameters.

If just going to assign it to something anyway.

# Conflicts:
# src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeManager.cs

* Return type directly from GetBestAccessibleType()

The type passed in can never be ParameterModifierType or PointerType so
the case where false is returned can never be hit. Change to returning
the accessible type directly, and remove branches for false returns.

# Conflicts:
# src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeManager.cs

* Add tests for getting best accessible type.

Commit migrated from https://github.com/dotnet/corefx/commit/ee64cb0126834cf8563d22db6fc73cdffc9ac4cd

src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/RuntimeBinder.cs
src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/MethodTypeInferrer.cs
src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Types/TypeManager.cs
src/libraries/Microsoft.CSharp/tests/AccessTests.cs [new file with mode: 0644]
src/libraries/Microsoft.CSharp/tests/AccessTests.netcoreapp.cs
src/libraries/Microsoft.CSharp/tests/Microsoft.CSharp.Tests.csproj

index 5f8111a..c15dcf8 100644 (file)
@@ -274,17 +274,13 @@ namespace Microsoft.CSharp.RuntimeBinder
 
                 // This ensures that the type we pick is something that the user could have written.
 
-                CType actualType = _symbolTable.GetCTypeFromType(t);
-                CType bestType;
-
-                bool res = _semanticChecker.GetTypeManager().GetBestAccessibleType(_semanticChecker, _bindingContext, actualType, out bestType);
-
                 // Since the actual type of these arguments are never going to be pointer
                 // types or ref/out types (they are in fact boxed into an object), we have
                 // a guarantee that we will always be able to find a best accessible type
                 // (which, in the worst case, may be object).
-                Debug.Assert(res, "Unexpected failure of GetBestAccessibleType in construction of argument array");
 
+                CType actualType = _symbolTable.GetCTypeFromType(t);
+                CType bestType = _semanticChecker.GetTypeManager().GetBestAccessibleType(_semanticChecker, _bindingContext.ContextForMemberLookup, actualType);
                 t = bestType.AssociatedSystemType;
             }
 
index e53fe3e..24c26c3 100644 (file)
@@ -1704,31 +1704,16 @@ namespace Microsoft.CSharp.RuntimeBinder.Semantics
             // used to alter the types at the beginning of binding. that
             // way we get an accessible type, and if it so happens that
             // the selected type is inappropriate (for conversions) then
-            // we let overload resolution sort it out. 
+            // we let overload resolution sort it out.
             //
             // since we can never infer ref/out or pointer types here, we
-            // are more or less guaranteed a best accessible type. However,
-            // in the interest of safety, if it becomes impossible to
-            // choose a "best accessible" type, then we will fail type
-            // inference so we do not try to pass the inaccessible type
-            // back to overload resolution.
-
-            CType pBestAccessible;
-            if (GetTypeManager().GetBestAccessibleType(_binder.GetSemanticChecker(), _binder.GetContext(), pBest, out pBestAccessible))
-            {
-                pBest = pBestAccessible;
-            }
-            else
-            {
-                Debug.Assert(false, "Method type inference could not find an accessible type over the best candidate in fixed");
-                return false;
-            }
+            // are guaranteed a best accessible type.
 
+            _pFixedResults[iParam] = GetTypeManager().GetBestAccessibleType(_binder.GetSemanticChecker(), _binder.GetContext().ContextForMemberLookup, pBest);
             // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
             // END RUNTIME BINDER ONLY CHANGE
             // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-            _pFixedResults[iParam] = pBest;
             UpdateDependenciesAfterFix(iParam);
             return true;
         }
index 3cb378d..90f48fc 100644 (file)
@@ -626,7 +626,7 @@ namespace Microsoft.CSharp.RuntimeBinder.Semantics
         // RUNTIME BINDER ONLY CHANGE
         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-        internal bool GetBestAccessibleType(CSemanticChecker semanticChecker, BindingContext bindingContext, CType typeSrc, out CType typeDst)
+        internal CType GetBestAccessibleType(CSemanticChecker semanticChecker, AggregateDeclaration context, CType typeSrc)
         {
             // This method implements the "best accessible type" algorithm for determining the type
             // of untyped arguments in the runtime binder. It is also used in method type inference
@@ -636,91 +636,75 @@ namespace Microsoft.CSharp.RuntimeBinder.Semantics
             // non-null) only when the algorithm could find a suitable accessible type.
 
             Debug.Assert(semanticChecker != null);
-            Debug.Assert(bindingContext != null);
             Debug.Assert(typeSrc != null);
+            Debug.Assert(!(typeSrc is ParameterModifierType));
+            Debug.Assert(!(typeSrc is PointerType));
 
-            typeDst = null;
-
-            if (semanticChecker.CheckTypeAccess(typeSrc, bindingContext.ContextForMemberLookup))
+            if (semanticChecker.CheckTypeAccess(typeSrc, context))
             {
-                // If we already have an accessible type, then use it. This is the terminal point of the recursion.
-                typeDst = typeSrc;
-                return true;
+                // If we already have an accessible type, then use it.
+                return typeSrc;
             }
 
             // These guys have no accessibility concerns.
             Debug.Assert(!(typeSrc is VoidType) && !(typeSrc is TypeParameterType));
 
-            if (typeSrc is ParameterModifierType || typeSrc is PointerType)
-            {
-                // We cannot vary these.
-                return false;
-            }
-
-            CType intermediateType;
-            if (typeSrc is AggregateType aggSrc && (aggSrc.IsInterfaceType || aggSrc.IsDelegateType) && TryVarianceAdjustmentToGetAccessibleType(semanticChecker, bindingContext, aggSrc, out intermediateType))
+            if (typeSrc is AggregateType aggSrc)
             {
-                // If we have an interface or delegate type, then it can potentially be varied by its type arguments
-                // to produce an accessible type, and if that's the case, then return that.
-                // Example: IEnumerable<PrivateConcreteFoo> --> IEnumerable<PublicAbstractFoo>
-                typeDst = intermediateType;
+                for (;;)
+                {
+                    if ((aggSrc.IsInterfaceType || aggSrc.IsDelegateType) && TryVarianceAdjustmentToGetAccessibleType(semanticChecker, context, aggSrc, out CType typeDst))
+                    {
+                        // If we have an interface or delegate type, then it can potentially be varied by its type arguments
+                        // to produce an accessible type, and if that's the case, then return that.
+                        // Example: IEnumerable<PrivateConcreteFoo> --> IEnumerable<PublicAbstractFoo>
+                        Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, context));
+                        return typeDst;
+                    }
 
-                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
-                return true;
-            }
+                    // We have an AggregateType, so recurse on its base class.
+                    AggregateType baseType = aggSrc.BaseClass;
+                    if (baseType == null)
+                    {
+                        // This happens with interfaces, for instance. But in that case, the
+                        // conversion to object does exist, is an implicit reference conversion,
+                        // and is guaranteed to be accessible, so we will use it.
+                        return GetPredefAgg(PredefinedType.PT_OBJECT).getThisType();
+                    }
 
-            if (typeSrc is ArrayType arrSrc && TryArrayVarianceAdjustmentToGetAccessibleType(semanticChecker, bindingContext, arrSrc, out intermediateType))
-            {
-                // Similarly to the interface and delegate case, arrays are covariant in their element type and
-                // so we can potentially produce an array type that is accessible.
-                // Example: PrivateConcreteFoo[] --> PublicAbstractFoo[]
-                typeDst = intermediateType;
+                    if (semanticChecker.CheckTypeAccess(baseType, context))
+                    {
+                        return baseType;
+                    }
 
-                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
-                return true;
+                    // baseType is always an AggregateType, so no need for logic of other types.
+                    aggSrc = baseType;
+                }
             }
 
-            if (typeSrc is NullableType)
+            if (typeSrc is ArrayType arrSrc)
             {
-                // We have an inaccessible nullable type, which means that the best we can do is System.ValueType.
-                typeDst = GetPredefAgg(PredefinedType.PT_VALUE).getThisType();
-
-                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
-                return true;
-            }
+                if (TryArrayVarianceAdjustmentToGetAccessibleType(semanticChecker, context, arrSrc, out CType typeDst))
+                {
+                    // Similarly to the interface and delegate case, arrays are covariant in their element type and
+                    // so we can potentially produce an array type that is accessible.
+                    // Example: PrivateConcreteFoo[] --> PublicAbstractFoo[]
+                    Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, context));
+                    return typeDst;
+                }
 
-            if (typeSrc is ArrayType)
-            {
                 // We have an inaccessible array type for which we could not earlier find a better array type
                 // with a covariant conversion, so the best we can do is System.Array.
-                typeDst = GetPredefAgg(PredefinedType.PT_ARRAY).getThisType();
-
-                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
-                return true;
+                return GetPredefAgg(PredefinedType.PT_ARRAY).getThisType();
             }
 
-            Debug.Assert(typeSrc is AggregateType);
-
-            if (typeSrc is AggregateType aggType)
-            {
-                // We have an AggregateType, so recurse on its base class.
-                AggregateType baseType = aggType.BaseClass;
-
-                if (baseType == null)
-                {
-                    // This happens with interfaces, for instance. But in that case, the
-                    // conversion to object does exist, is an implicit reference conversion,
-                    // and so we will use it.
-                    baseType = GetPredefAgg(PredefinedType.PT_OBJECT).getThisType();
-                }
+            Debug.Assert(typeSrc is NullableType);
 
-                return GetBestAccessibleType(semanticChecker, bindingContext, baseType, out typeDst);
-            }
-
-            return false;
+            // We have an inaccessible nullable type, which means that the best we can do is System.ValueType.
+            return GetPredefAgg(PredefinedType.PT_VALUE).getThisType();
         }
 
-        private bool TryVarianceAdjustmentToGetAccessibleType(CSemanticChecker semanticChecker, BindingContext bindingContext, AggregateType typeSrc, out CType typeDst)
+        private bool TryVarianceAdjustmentToGetAccessibleType(CSemanticChecker semanticChecker, AggregateDeclaration context, AggregateType typeSrc, out CType typeDst)
         {
             Debug.Assert(typeSrc != null);
             Debug.Assert(typeSrc.IsInterfaceType || typeSrc.IsDelegateType);
@@ -730,7 +714,7 @@ namespace Microsoft.CSharp.RuntimeBinder.Semantics
             AggregateSymbol aggSym = typeSrc.OwningAggregate;
             AggregateType aggOpenType = aggSym.getThisType();
 
-            if (!semanticChecker.CheckTypeAccess(aggOpenType, bindingContext.ContextForMemberLookup))
+            if (!semanticChecker.CheckTypeAccess(aggOpenType, context))
             {
                 // if the aggregate symbol itself is not accessible, then forget it, there is no
                 // variance that will help us arrive at an accessible type.
@@ -741,78 +725,65 @@ namespace Microsoft.CSharp.RuntimeBinder.Semantics
             TypeArray typeParams = aggOpenType.TypeArgsThis;
             CType[] newTypeArgsTemp = new CType[typeArgs.Count];
 
-            for (int i = 0; i < typeArgs.Count; i++)
+            for (int i = 0; i < newTypeArgsTemp.Length; i++)
             {
-                if (semanticChecker.CheckTypeAccess(typeArgs[i], bindingContext.ContextForMemberLookup))
+                CType typeArg = typeArgs[i];
+                if (semanticChecker.CheckTypeAccess(typeArg, context))
                 {
                     // we have an accessible argument, this position is not a problem.
-                    newTypeArgsTemp[i] = typeArgs[i];
+                    newTypeArgsTemp[i] = typeArg;
                     continue;
                 }
 
-                if (!typeArgs[i].IsReferenceType || !((TypeParameterType)typeParams[i]).Covariant)
+                if (!typeArg.IsReferenceType || !((TypeParameterType)typeParams[i]).Covariant)
                 {
                     // This guy is inaccessible, and we are not going to be able to vary him, so we need to fail.
                     return false;
                 }
 
-                CType intermediateTypeArg;
-                if (GetBestAccessibleType(semanticChecker, bindingContext, typeArgs[i], out intermediateTypeArg))
-                {
-                    // now we either have a value type (which must be accessible due to the above
-                    // check, OR we have an inaccessible type (which must be a ref type). In either
-                    // case, the recursion worked out and we are OK to vary this argument.
-                    newTypeArgsTemp[i] = intermediateTypeArg;
-                    continue;
-                }
-                else
-                {
-                    Debug.Assert(false, "GetBestAccessibleType unexpectedly failed on a type that was used as a type parameter");
-                    return false;
-                }
+                newTypeArgsTemp[i] = GetBestAccessibleType(semanticChecker, context, typeArg);
+
+                // now we either have a value type (which must be accessible due to the above
+                // check, OR we have an inaccessible type (which must be a ref type). In either
+                // case, the recursion worked out and we are OK to vary this argument.
             }
 
             TypeArray newTypeArgs = semanticChecker.getBSymmgr().AllocParams(typeArgs.Count, newTypeArgsTemp);
-            CType intermediateType = this.GetAggregate(aggSym, typeSrc.OuterType, newTypeArgs);
+            CType intermediateType = GetAggregate(aggSym, typeSrc.OuterType, newTypeArgs);
 
             // All type arguments were varied successfully, which means now we must be accessible. But we could
             // have violated constraints. Let's check that out.
 
-            if (!TypeBind.CheckConstraints(semanticChecker, null/*ErrorHandling*/, intermediateType, CheckConstraintsFlags.NoErrors))
+            if (!TypeBind.CheckConstraints(semanticChecker, errHandling: null, intermediateType, CheckConstraintsFlags.NoErrors))
             {
                 return false;
             }
 
             typeDst = intermediateType;
-            Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
+            Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, context));
             return true;
         }
 
-        private bool TryArrayVarianceAdjustmentToGetAccessibleType(CSemanticChecker semanticChecker, BindingContext bindingContext, ArrayType typeSrc, out CType typeDst)
+        private bool TryArrayVarianceAdjustmentToGetAccessibleType(CSemanticChecker semanticChecker, AggregateDeclaration context, ArrayType typeSrc, out CType typeDst)
         {
             Debug.Assert(typeSrc != null);
 
-            typeDst = null;
-
             // We are here because we have an array type with an inaccessible element type. If possible,
             // we should create a new array type that has an accessible element type for which a
             // conversion exists.
 
             CType elementType = typeSrc.ElementType;
-            if (!elementType.IsReferenceType)
-            {
-                // Covariant array conversions exist for reference types only.
-                return false;
-            }
-
-            if (GetBestAccessibleType(semanticChecker, bindingContext, elementType, out CType intermediateType))
+            // Covariant array conversions exist for reference types only.
+            if (elementType.IsReferenceType)
             {
-                typeDst = GetArray(intermediateType, typeSrc.Rank, typeSrc.IsSZArray);
+                CType destElement = GetBestAccessibleType(semanticChecker, context, elementType);
+                typeDst = GetArray(destElement, typeSrc.Rank, typeSrc.IsSZArray);
 
-                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, bindingContext.ContextForMemberLookup));
+                Debug.Assert(semanticChecker.CheckTypeAccess(typeDst, context));
                 return true;
             }
 
+            typeDst = null;
             return false;
         }
 
diff --git a/src/libraries/Microsoft.CSharp/tests/AccessTests.cs b/src/libraries/Microsoft.CSharp/tests/AccessTests.cs
new file mode 100644 (file)
index 0000000..f2c4d54
--- /dev/null
@@ -0,0 +1,188 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+namespace Microsoft.CSharp.RuntimeBinder.Tests
+{
+    public partial class AccessTests
+    {
+        public abstract class PublicReferenceType
+        {
+            public abstract int IntValueProperty { get; }
+        }
+
+        public interface ITestIFace
+        {
+        }
+
+        public interface ITestIFaceCons<out T> where T : ITestIFace
+        {
+        }
+
+        private static class Container
+        {
+            private abstract class ReferenceTypeIntermediary : PublicReferenceType
+            {
+            }
+
+            private class ReferenceType : ReferenceTypeIntermediary, ITestIFace
+            {
+                public override int IntValueProperty => 23;
+            }
+
+            private struct PrivateValueType
+            {
+            }
+
+            private interface IPrivateInterface
+            {
+            }
+
+            public static dynamic GetReferenceType() => new ReferenceType();
+
+            public static dynamic TenReferenceTypesArray() => new ReferenceType[10];
+
+            public static dynamic TenReferenceTypesRepeat() => Enumerable.Repeat(new ReferenceType(), 10);
+
+            public static dynamic ReferenceTypeDelegate() =>
+                (Func<int, IEnumerable<ReferenceType>>)(i => Enumerable.Repeat(new ReferenceType(), i));
+
+            public static dynamic ValueTypeArray() => new PrivateValueType[2];
+
+            private delegate int PrivateFunc<T>(T arg);
+
+            public static dynamic PrivateDelegateType() => (PrivateFunc<ReferenceType>)(r => r.IntValueProperty);
+
+            public static dynamic ValueTypeDelegate() => (Func<PrivateValueType>)(() => new PrivateValueType());
+
+            public unsafe static dynamic PointerArray() => new PrivateValueType*[4];
+
+            public static dynamic PrivateInterfaceDelegate() => (Func<IPrivateInterface>)(() => null);
+
+            public static dynamic PrivateConstraintInterfaceDelegate() => (Func<ITestIFaceCons<ReferenceType>>)(() => null);
+        }
+
+        [Fact]
+        public void CanGetAccessibleBaseOfInaccessibleType() => Assert.Equal(23, Container.GetReferenceType().IntValueProperty);
+
+        [Fact]
+        public void AccessibleArray() => Assert.Equal(10, Enumerable.Count(Container.TenReferenceTypesArray()));
+
+        [Fact]
+        public void AccessibleInterface() => Assert.Equal(10, Enumerable.Count(Container.TenReferenceTypesRepeat()));
+
+        [Fact]
+        public void AccessCovariantDelegate()
+        {
+            IEnumerable<PublicReferenceType> prts = Container.ReferenceTypeDelegate()(4);
+            Assert.Equal(4, prts.Count());
+            foreach (PublicReferenceType prt in prts)
+            {
+                Assert.Equal(23, prt.IntValueProperty);
+            }
+        }
+
+        [Fact]
+        public void NonCovariantArrayToArrayType() => Assert.Equal(2, Container.ValueTypeArray().Length);
+
+        [Fact]
+        public void NonCovariantArrayNotCastToIndexableType()
+        {
+            dynamic array = Container.ValueTypeArray();
+            Assert.Throws<RuntimeBinderException>(() => array[0]);
+        }
+
+        [Fact]
+        public void PointerArrayToArrayType() => Assert.Equal(4, Container.PointerArray().Length);
+
+        [Fact]
+        public void PointerArrayNotCastToIndexableType()
+        {
+            dynamic array = Container.PointerArray();
+            Assert.Throws<RuntimeBinderException>(() => array[0]);
+        }
+
+        [Fact]
+        public void PrivateDelegateType()
+        {
+            dynamic d = Container.PrivateDelegateType();
+            dynamic a = Container.GetReferenceType();
+            Assert.Throws<RuntimeBinderException>(() => d(a));
+            d.DynamicInvoke(a); // Can use as MulticastDelegate.
+        }
+
+        [Fact]
+        public void PrivateValueTypeDelegateType()
+        {
+            dynamic d = Container.ValueTypeDelegate();
+            Assert.Throws<RuntimeBinderException>(() => d());
+            ValueType result = d.DynamicInvoke(); // Can use as MulticastDelegate.
+        }
+
+        [Fact]
+        public void PrivateIFaceDelegateType()
+        {
+            dynamic d = Container.PrivateInterfaceDelegate();
+            Assert.Null(d());
+        }
+
+        [Fact]
+        public void PrivateInterfaceConstraint()
+        {
+            // Casting Func<ITestIFaceCons<ReferenceType>> to Func<ITestIFaceCons<PublicReferenceType>>
+            // would be illegal because ITestIFaceCons<PublicReferenceType> violates the ITestIFaceCons
+            // constraints, so the binder has to detect that, and cast to Func<object>.
+            dynamic d = Container.PrivateConstraintInterfaceDelegate();
+            Assert.Null(d());
+        }
+
+        private struct SomeValueType
+        {
+            public override string ToString() => "test";
+        }
+
+        [Fact]
+        public void NullableOfInaccessible()
+        {
+            // ValueType members work without access to the type.
+            CallSite<Func<CallSite, SomeValueType?, object>> site =
+                CallSite<Func<CallSite, SomeValueType?, object>>.Create(
+                    Binder.InvokeMember(
+                        CSharpBinderFlags.None, "ToString", null, null,
+                        new[]
+                        {
+                            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+                        }));
+            Func<CallSite, SomeValueType?, object> target = site.Target;
+            Assert.Equal("test", target(site, new SomeValueType()));
+
+            // Nullable<T> members work with access to the type.
+            site = CallSite<Func<CallSite, SomeValueType?, object>>.Create(
+                Binder.InvokeMember(
+                    CSharpBinderFlags.None, "GetValueOrDefault", null, GetType(),
+                    new[]
+                    {
+                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+                    }));
+            target = site.Target;
+            Assert.Equal(new SomeValueType(), target(site, new SomeValueType()));
+
+            // Nullable<T> members don't work without access to the type.
+            site = CallSite<Func<CallSite, SomeValueType?, object>>.Create(
+                Binder.InvokeMember(
+                    CSharpBinderFlags.None, "GetValueOrDefault", null, null,
+                    new[]
+                    {
+                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
+                    }));
+            target = site.Target;
+            Assert.Throws<RuntimeBinderException>(() => target(site, new SomeValueType()));
+        }
+    }
+}
index a7cfdf6..9e62a92 100644 (file)
@@ -10,7 +10,7 @@ using Xunit;
 
 namespace Microsoft.CSharp.RuntimeBinder.Tests
 {
-    public class AccessTests
+    public partial class AccessTests
     {
         private readonly Type _baseType;
         private readonly Type _siblingType;
index db08891..320b4ef 100644 (file)
@@ -3,12 +3,14 @@
   <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
   <PropertyGroup>
     <ProjectGuid>{82B54697-0251-47A1-8546-FC507D0F3B08}</ProjectGuid>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Debug|AnyCPU'" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Release|AnyCPU'" />
   <ItemGroup>
+    <Compile Include="AccessTests.cs" />
     <Compile Include="ArrayHandling.cs" />
     <Compile Include="AssignmentTests.cs" />
     <Compile Include="BindingErrors.cs" />