Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0f5267d

Browse files
JonHannaVSadov
authored andcommitted
Allow dynamic use objects of different, identically-named, types. (#25191)
* Allow dynamic use objects of different, identically-named, types. Fixes #25190 * Remove ResetBindException No longer used. * Cache types' arity for FindSymWithMatchingArity instead of on each loop * Move byref handling out of LoadSymbolsFromType() Only one caller can possibly pass byref types, so handle it there. * Further simplify type matching. Since we're looking to match types, and types with different arity don't match, just look for the type itself. * Remove dead code and tidy up. Paths within the case of the type .IsNullableType() handle the possibility of the type itself being Nullable<T>, but that type returns false for the nullable type check (possibly not true in the static compiler?). Remove the branches for that, and tidy up. * Move handling of nullable types. Since there can be corresponding AggregateSymbols we have to test for it anyway, so may as well test before any more work is done. * Use InsertRange instead of repeated inserts. Fewer allocations and copies.
1 parent a77d5aa commit 0f5267d

File tree

6 files changed

+74
-197
lines changed

6 files changed

+74
-197
lines changed

src/Microsoft.CSharp/src/Microsoft.CSharp.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
<Compile Include="Microsoft\CSharp\RuntimeBinder\ExpressionTreeCallRewriter.cs" />
4242
<Compile Include="Microsoft\CSharp\RuntimeBinder\ICSharpBinder.cs" />
4343
<Compile Include="Microsoft\CSharp\RuntimeBinder\ICSharpInvokeOrInvokeMemberBinder.cs" />
44-
<Compile Include="Microsoft\CSharp\RuntimeBinder\ResetBindException.cs" />
4544
<Compile Include="Microsoft\CSharp\RuntimeBinder\RuntimeBinder.cs" />
4645
<Compile Include="Microsoft\CSharp\RuntimeBinder\RuntimeBinderException.cs" />
4746
<Compile Include="Microsoft\CSharp\RuntimeBinder\RuntimeBinderExtensions.cs" />

src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ResetBindException.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/RuntimeBinder.cs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -103,35 +103,7 @@ public Expression Bind(
103103

104104
lock (_bindLock)
105105
{
106-
// this is a strategy for realizing correct binding when the symboltable
107-
// finds a name collision across different types, e.g. one dynamic binding
108-
// uses a type "N.T" and now a second binding uses a different type "N.T".
109-
110-
// In order to make this work, we have to reset the symbol table and begin
111-
// the second binding over again when we detect the collision. So this is
112-
// something like a longjmp to the beginning of binding. For a single binding,
113-
// if we have to do this more than once, we give an RBE--this would be a
114-
// scenario that needs to know about both N.T's simultaneously to work.
115-
116-
// See SymbolTable.LoadSymbolsFromType for more information.
117-
118-
try
119-
{
120-
return BindCore(binder, parameters, args, out deferredBinding);
121-
}
122-
catch (ResetBindException)
123-
{
124-
Reset();
125-
try
126-
{
127-
return BindCore(binder, parameters, args, out deferredBinding);
128-
}
129-
catch (ResetBindException)
130-
{
131-
Reset();
132-
throw Error.BindingNameCollision();
133-
}
134-
}
106+
return BindCore(binder, parameters, args, out deferredBinding);
135107
}
136108
}
137109

src/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/SymbolTable.cs

Lines changed: 49 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -638,17 +638,9 @@ private TypeParameterType AddTypeParameterToSymbolTable(
638638
#region LoadTypeChain
639639
/////////////////////////////////////////////////////////////////////////////////
640640

641-
private CType LoadSymbolsFromType(Type originalType)
641+
private CType LoadSymbolsFromType(Type type)
642642
{
643-
List<object> declarationChain = BuildDeclarationChain(originalType);
644-
645-
Type type = originalType;
646-
CType ret = null;
647-
bool bIsByRef = type.IsByRef;
648-
if (bIsByRef)
649-
{
650-
type = type.GetElementType();
651-
}
643+
List<object> declarationChain = BuildDeclarationChain(type);
652644

653645
NamespaceOrAggregateSymbol current = NamespaceSymbol.Root;
654646

@@ -657,106 +649,55 @@ private CType LoadSymbolsFromType(Type originalType)
657649
for (int i = 0; i < declarationChain.Count; i++)
658650
{
659651
object o = declarationChain[i];
660-
NamespaceOrAggregateSymbol next;
661-
if (o is Type)
652+
if (o is Type t)
662653
{
663-
Type t = o as Type;
664-
next = _symbolTable.LookupSym(GetName(t), current, symbmask_t.MASK_AggregateSymbol) as AggregateSymbol;
665-
666-
// Make sure we match arity as well when we find an aggregate.
667-
if (next != null)
654+
if (t.IsNullableType())
668655
{
669-
next = FindSymWithMatchingArity(next as AggregateSymbol, t);
656+
return _typeManager.GetNullable(GetCTypeFromType(t.GetGenericArguments()[0]));
670657
}
671658

672-
// In the event that two different types exist that have the same name, they
673-
// cannot both have entries in the symbol table with our current architecture.
674-
// This can happen in dynamic, since the runtime binder lives across all
675-
// call sites in an appdomain, and assemblies can have been loaded at runtime
676-
// that have different types with the same name.
677-
678-
// In the real compiler, this would have been an error and name lookup would
679-
// be ambiguous, but here we never have to lookup names of types for real (only
680-
// names of members).
681-
682-
// The tactical fix is this: if we encounter this situation, where we have
683-
// identically named types that are not the same, then we are going to clear
684-
// the entire symbol table and restart this binding. This solution is not
685-
// without its own problems, since it is possible to conceive of a single
686-
// dynamic binding that needs to simultaneously know about both of the
687-
// similarly named types, but we are not going to try to solve that
688-
// scenario here.
689-
690-
if (next != null)
691-
{
692-
Type existingType = (next as AggregateSymbol).AssociatedSystemType;
693-
Type newType = t.IsGenericType ? t.GetGenericTypeDefinition() : t;
694-
695-
// We use "IsEquivalentTo" so that unified local types for NoPIA do
696-
// not trigger a reset. There are other mechanisms to make those sorts
697-
// of types work in some scenarios.
698-
if (!existingType.IsEquivalentTo(newType))
699-
{
700-
throw new ResetBindException();
701-
}
702-
}
659+
AggregateSymbol next = FindSymForType(
660+
_symbolTable.LookupSym(GetName(t), current, symbmask_t.MASK_AggregateSymbol), t);
703661

704662
// If we haven't found this type yet, then add it to our symbol table.
705-
if (next == null || t.IsNullableType())
663+
if (next == null)
706664
{
707665
// Note that if we have anything other than an AggregateSymbol,
708666
// we must be at the end of the line - that is, nothing else can
709667
// have children.
710-
711668
CType ctype = ProcessSpecialTypeInChain(current, t);
712669
if (ctype != null)
713670
{
714-
// If we had an aggregate type, its possible we're not at the end.
715-
// This will happen for nullable<T> for instance.
716-
if (ctype is AggregateType cat)
717-
{
718-
next = cat.GetOwningAggregate();
719-
}
720-
else
721-
{
722-
ret = ctype;
723-
break;
724-
}
725-
}
726-
else
727-
{
728-
// This is a regular class.
729-
next = AddAggregateToSymbolTable(current, t);
671+
Debug.Assert(!(ctype is AggregateType));
672+
return ctype;
730673
}
674+
675+
// This is a regular class.
676+
next = AddAggregateToSymbolTable(current, t);
731677
}
732678

733679
if (t == type)
734680
{
735-
ret = GetConstructedType(type, next as AggregateSymbol);
736-
break;
681+
return GetConstructedType(type, next);
737682
}
683+
684+
current = next;
738685
}
739-
else if (o is MethodInfo)
686+
else if (o is MethodInfo m)
740687
{
741688
// We cant be at the end.
742689
Debug.Assert(i + 1 < declarationChain.Count);
743-
ret = ProcessMethodTypeParameter(o as MethodInfo, declarationChain[++i] as Type, current as AggregateSymbol);
744-
break;
690+
return ProcessMethodTypeParameter(m, declarationChain[++i] as Type, current as AggregateSymbol);
745691
}
746692
else
747693
{
748694
Debug.Assert(o is string);
749-
next = AddNamespaceToSymbolTable(current, o as string);
695+
current = AddNamespaceToSymbolTable(current, o as string);
750696
}
751-
current = next;
752697
}
753698

754-
Debug.Assert(ret != null);
755-
if (bIsByRef)
756-
{
757-
ret = _typeManager.GetParameterModifier(ret, false);
758-
}
759-
return ret;
699+
Debug.Fail("Should be unreachable");
700+
return null;
760701
}
761702

762703
/////////////////////////////////////////////////////////////////////////////////
@@ -804,18 +745,17 @@ private CType GetConstructedType(Type type, AggregateSymbol agg)
804745

805746
private CType ProcessSpecialTypeInChain(NamespaceOrAggregateSymbol parent, Type t)
806747
{
807-
CType ctype;
808748
if (t.IsGenericParameter)
809749
{
810750
AggregateSymbol agg = parent as AggregateSymbol;
811751
Debug.Assert(agg != null);
812-
ctype = LoadClassTypeParameter(agg, t);
813-
return ctype;
752+
return LoadClassTypeParameter(agg, t);
814753
}
815-
else if (t.IsArray)
754+
755+
if (t.IsArray)
816756
{
817757
// Now we return an array of nesting level corresponding to the rank.
818-
ctype = _typeManager.GetArray(
758+
return _typeManager.GetArray(
819759
GetCTypeFromType(t.GetElementType()),
820760
t.GetArrayRank(),
821761
#if netcoreapp
@@ -824,37 +764,14 @@ private CType ProcessSpecialTypeInChain(NamespaceOrAggregateSymbol parent, Type
824764
t.GetElementType().MakeArrayType() == t
825765
#endif
826766
);
827-
return ctype;
828767
}
829-
else if (t.IsPointer)
768+
769+
if (t.IsPointer)
830770
{
831771
// Now we return the pointer type that we want.
832-
ctype = _typeManager.GetPointer(GetCTypeFromType(t.GetElementType()));
833-
return ctype;
834-
}
835-
else if (t.IsNullableType())
836-
{
837-
// Get a nullable type of the underlying type.
838-
if (t.GetGenericArguments()[0].DeclaringType == t)
839-
{
840-
// If the generic argument for nullable is our child, then we're
841-
// declaring the initial Nullable<T>.
842-
AggregateSymbol agg = _symbolTable.LookupSym(
843-
GetName(t), parent, symbmask_t.MASK_AggregateSymbol) as AggregateSymbol;
844-
if (agg != null)
845-
{
846-
agg = FindSymWithMatchingArity(agg, t);
847-
if (agg != null)
848-
{
849-
Debug.Assert(agg.getThisType().AssociatedSystemType == t);
850-
return agg.getThisType();
851-
}
852-
}
853-
return AddAggregateToSymbolTable(parent, t).getThisType();
854-
}
855-
ctype = _typeManager.GetNullable(GetCTypeFromType(t.GetGenericArguments()[0]));
856-
return ctype;
772+
return _typeManager.GetPointer(GetCTypeFromType(t.GetElementType()));
857773
}
774+
858775
return null;
859776
}
860777

@@ -906,40 +823,41 @@ private static List<object> BuildDeclarationChain(Type callingType)
906823
Debug.Assert(bAdded);
907824
}
908825
}
826+
909827
callChain.Reverse();
910828

911829
// Now take out the namespaces and add them to the end of the chain.
912-
913830
if (callingType.Namespace != null)
914831
{
915-
string[] namespaces = callingType.Namespace.Split('.');
916-
int index = 0;
917-
foreach (string s in namespaces)
918-
{
919-
callChain.Insert(index++, s);
920-
}
832+
callChain.InsertRange(0, callingType.Namespace.Split('.'));
921833
}
922834
return callChain;
923835
}
924836

925-
/////////////////////////////////////////////////////////////////////////////////
837+
// We have an aggregate symbol of the correct parent and full name, but it may have the wrong arity, or, due to
838+
// dynamic loading or creation, two different types can exist that have the same name.
839+
840+
// In the static compiler, this would have been an error and name lookup would be ambiguous, but here we never have
841+
// to lookup names of types for real (only names of members).
926842

927-
private AggregateSymbol FindSymWithMatchingArity(AggregateSymbol aggregateSymbol, Type type)
843+
// For either case, move onto the next symbol in the chain, and check again for appropriate type.
844+
private AggregateSymbol FindSymForType(Symbol sym, Type t)
928845
{
929-
for (AggregateSymbol agg = aggregateSymbol;
930-
agg != null;
931-
agg = BSYMMGR.LookupNextSym(agg, agg.Parent, symbmask_t.MASK_AggregateSymbol) as AggregateSymbol)
846+
while (sym != null)
932847
{
933-
if (agg.GetTypeVarsAll().Count == type.GetGenericArguments().Length)
848+
// We use "IsEquivalentTo" so that unified local types match.
849+
if (sym is AggregateSymbol agg)
850+
if (agg.AssociatedSystemType.IsEquivalentTo(t.IsGenericType ? t.GetGenericTypeDefinition() : t))
934851
{
935852
return agg;
936853
}
854+
855+
sym = sym.nextSameName;
937856
}
857+
938858
return null;
939859
}
940860

941-
/////////////////////////////////////////////////////////////////////////////////
942-
943861
private NamespaceSymbol AddNamespaceToSymbolTable(NamespaceOrAggregateSymbol parent, string sz)
944862
{
945863
Name name = GetName(sz);
@@ -974,10 +892,10 @@ internal CType[] GetCTypeArrayFromTypes(Type[] types)
974892

975893
/////////////////////////////////////////////////////////////////////////////////
976894

977-
internal CType GetCTypeFromType(Type t)
978-
{
979-
return LoadSymbolsFromType(t);
980-
}
895+
internal CType GetCTypeFromType(Type type) => type.IsByRef
896+
? _typeManager.GetParameterModifier(LoadSymbolsFromType(type.GetElementType()), false)
897+
: LoadSymbolsFromType(type);
898+
981899
#endregion
982900

983901
#region Aggregates

src/System.Linq.Expressions/tests/Dynamic/BinaryOperationTests.cs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
using System.Linq;
77
using System.Linq.Expressions;
88
using System.Linq.Expressions.Tests;
9-
using System.Reflection;
10-
using System.Reflection.Emit;
11-
using System.Runtime.CompilerServices;
129
using Microsoft.CSharp.RuntimeBinder;
1310
using Xunit;
1411

@@ -748,27 +745,6 @@ public void BinaryCallSiteBinder_DynamicExpression(bool useInterpreter)
748745
Assert.Equal("42", func().ToString());
749746
}
750747

751-
#if FEATURE_COMPILE // We're not testing compilation, but we do need Reflection.Emit for the test
752-
[Fact]
753-
public void OperationOnTwoObjectsDifferentTypesOfSameName()
754-
{
755-
object objX = Activator.CreateInstance(
756-
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAssembly"), AssemblyBuilderAccess.Run)
757-
.DefineDynamicModule("TestModule").DefineType("TestType", TypeAttributes.Public).CreateType());
758-
object objY = Activator.CreateInstance(
759-
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAssembly"), AssemblyBuilderAccess.Run)
760-
.DefineDynamicModule("TestModule").DefineType("TestType", TypeAttributes.Public).CreateType());
761-
762-
CallSiteBinder binder =
763-
Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(
764-
CSharpBinderFlags.None, ExpressionType.Equal,
765-
GetType(), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
766-
var cs = CallSite<Func<CallSite, object, object, object>>.Create(binder);
767-
var t = cs.Target;
768-
Assert.Throws<RuntimeBinderException>(() => t(cs, objX, objY));
769-
}
770-
#endif
771-
772748
private class BinaryCallSiteBinder : BinaryOperationBinder
773749
{
774750
public BinaryCallSiteBinder() : base(ExpressionType.Add) {}

0 commit comments

Comments
 (0)