diff --git a/src/Engine/ProtoCore/Lang/CallSite.cs b/src/Engine/ProtoCore/Lang/CallSite.cs index d31ee5ae8df..1b948b72ec2 100644 --- a/src/Engine/ProtoCore/Lang/CallSite.cs +++ b/src/Engine/ProtoCore/Lang/CallSite.cs @@ -1118,23 +1118,35 @@ private FunctionEndPoint SelectFEPFromMultiple(StackFrame stackFrame, RuntimeCor Validity.Assert(svThisPtr.IsPointer, "this pointer wasn't a pointer. {89635B06-AD53-4170-ADA5-065EB2AE5858}"); - int typeID = svThisPtr.metaData.type; - - //Test for exact match - List exactFeps = new List(); - - foreach (FunctionEndPoint fep in feps) - if (fep.ClassOwnerIndex == typeID) - exactFeps.Add(fep); - - if (exactFeps.Count == 1) - { - return exactFeps[0]; - } - + // We have multiple possible scopes for the function call: + // 1. Static method call - no this pointer + // ex: ClassA.Method(); + // Hidden static methods generate multiple feps. + // We do not need to check actually if the method has the "IsHideBySig" (https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.ishidebysig) + // because static methods can only be hidden. + // 2. Method call from an instance of a class - valid this pointer. + // ex: classAInstance.method(); + // 3. Function from the global scope - no this pointer and no class scope. + // ex: SomeGlobalFunction(); + // + // All 3 cases will run through the same matching steps. + + // A static function call has an invalid this pointer and a valid class scope; + bool isValidStaticFuncCall = svThisPtr.Pointer == Constants.kInvalidIndex && stackFrame.ClassScope != Constants.kInvalidIndex; + + int typeID = isValidStaticFuncCall ? stackFrame.ClassScope : svThisPtr.metaData.type; + + // Try to match with feps belonging to the class scope (most derived class should have priority). + // In this case we simply select the function that belongs to the calling class. + // The assumption here is that all function end points in "feps" have already been checked that they have the same signature. + IEnumerable exactFeps = feps.Where(x => x.ClassOwnerIndex == typeID); + if (exactFeps.Count() == 1) + { + return exactFeps.First(); + } + //Walk the class tree structure to find the method - while (runtimeCore.DSExecutable.classTable.ClassNodes[typeID].Base != Constants.kInvalidIndex) { typeID = runtimeCore.DSExecutable.classTable.ClassNodes[typeID].Base; @@ -1149,15 +1161,15 @@ private FunctionEndPoint SelectFEPFromMultiple(StackFrame stackFrame, RuntimeCor foreach (FunctionEndPoint fep in feps) { - int noArbitraries = 0; + int numArbitraryRanks = 0; for (int i = 0; i < argumentsList.Count; i++) { if (fep.FormalParams[i].rank == Constants.kArbitraryRank) - noArbitraries++; - - numberOfArbitraryRanks.Add(noArbitraries); + numArbitraryRanks++; } + + numberOfArbitraryRanks.Add(numArbitraryRanks); } int smallest = Int32.MaxValue; @@ -1461,6 +1473,7 @@ private StackValue DispatchNew( candidatesFeps.Add(fep); } } + funcGroup = new FunctionGroup(candidatesFeps); #endregion diff --git a/src/Engine/ProtoCore/Reflection/Mirror.cs b/src/Engine/ProtoCore/Reflection/Mirror.cs index 8c125d03ca3..d5eb1507a1e 100644 --- a/src/Engine/ProtoCore/Reflection/Mirror.cs +++ b/src/Engine/ProtoCore/Reflection/Mirror.cs @@ -335,26 +335,27 @@ internal ClassMirror(StackValue svData, ProtoCore.Core core) } /// - /// Returns the constructors and static methods and properties - /// belonging to the type and its base types + /// Returns a list of constructors and static methods and properties + /// belonging to the type and its base types. Filters out repeating names. /// /// public IEnumerable GetMembers() { // TODO: Factor out reflection functionality for both LibraryServices and Mirrors List members = new List(); + members.AddRange(GetConstructors()); + members.AddRange(GetFunctions().Where(m => m.IsStatic)); + members.AddRange(GetProperties().Where(m => m.IsStatic)); - IEnumerable baseClasses = this.GetClassHierarchy(); + IEnumerable baseClasses = GetClassHierarchy(); foreach (var baseClass in baseClasses) { - members.AddRange(baseClass.GetFunctions().Where(m => m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - members.AddRange(baseClass.GetProperties().Where(m => m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); + members.AddRange(baseClass.GetFunctions().Where(m => m.IsStatic)); + members.AddRange(baseClass.GetProperties().Where(m => m.IsStatic)); } - members.AddRange(this.GetConstructors().GroupBy(x => x.Name).Select(y => y.First())); - members.AddRange(this.GetFunctions().Where(m => m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - members.AddRange(this.GetProperties().Where(m => m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - return members; + // Return a list of members with unique names. + return members.GroupBy(x => x.Name).Select(x => x.First()).ToList(); } /// @@ -468,20 +469,28 @@ public IEnumerable GetOverloads(string methodName) /// /// Given a method name, return the matching list of /// constructors or static methods on this type and its base types + /// Excludes hidden methods from base types. /// /// /// public IEnumerable GetOverloadsOnType(string methodName) { List members = new List(); - IEnumerable baseClasses = this.GetClassHierarchy(); + members.AddRange(GetConstructors().Where(x => x.MethodName == methodName)); + members.AddRange(GetFunctions().Where(x => x.IsStatic && x.MethodName == methodName)); + + // Filter out hidden functions/properties: + // Create a set of unique function and constructor descriptions. + // In this case we use ToString() to get the unique description of the members (func signature, constructor names). + var derivedClassMembers = new HashSet(); + members.ForEach(x => derivedClassMembers.Add(x.ToString())); + + IEnumerable baseClasses = GetClassHierarchy(); foreach (var baseClass in baseClasses) { - members.AddRange(baseClass.GetFunctions().Where(x => x.IsStatic && x.MethodName == methodName)); + members.AddRange(baseClass.GetFunctions().Where(x => x.IsStatic && x.MethodName == methodName && !derivedClassMembers.Contains(x.ToString()))); } - - members.AddRange(this.GetConstructors().Where(x => x.MethodName == methodName)); - members.AddRange(this.GetFunctions().Where(x => x.IsStatic && x.MethodName == methodName)); + return members; } @@ -494,31 +503,37 @@ public IEnumerable GetOverloadsOnType(string methodName) public IEnumerable GetOverloadsOnInstance(string methodName) { List members = new List(); - IEnumerable baseClasses = this.GetClassHierarchy(); + IEnumerable baseClasses = GetClassHierarchy(); foreach (var baseClass in baseClasses) { members.AddRange(baseClass.GetFunctions().Where(x => !x.IsStatic && x.MethodName == methodName)); } - members.AddRange(this.GetFunctions().Where(x => !x.IsStatic && x.MethodName == methodName)); + members.AddRange(GetFunctions().Where(x => !x.IsStatic && x.MethodName == methodName)); return members; } + /// + /// Returns the instance methods and properties + /// belonging to the type and its base types. Filters out repeating names. + /// public IEnumerable GetInstanceMembers() { // TODO: Factor out reflection functionality for both LibraryServices and Mirrors + List members = new List(); + members.AddRange(GetFunctions().Where(m => !m.IsStatic)); + members.AddRange(GetProperties().Where(m => !m.IsStatic)); - IEnumerable baseClasses = this.GetClassHierarchy(); + IEnumerable baseClasses = GetClassHierarchy(); foreach (var baseClass in baseClasses) { - members.AddRange(baseClass.GetFunctions().Where(m => !m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - members.AddRange(baseClass.GetProperties().Where(m => !m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); + members.AddRange(baseClass.GetFunctions().Where(m => !m.IsStatic)); + members.AddRange(baseClass.GetProperties().Where(m => !m.IsStatic)); } - members.AddRange(this.GetFunctions().Where(m => !m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - members.AddRange(this.GetProperties().Where(m => !m.IsStatic).GroupBy(x => x.Name).Select(y => y.First())); - return members; + // Return a list of members with unique names. + return members.GroupBy(x => x.Name).Select(y => y.First()).ToList(); } public ClassAttributes GetClassAttributes() diff --git a/test/DynamoCoreTests/CodeBlockNodeTests.cs b/test/DynamoCoreTests/CodeBlockNodeTests.cs index 0c1d3b2ee9b..9b86ca2d319 100644 --- a/test/DynamoCoreTests/CodeBlockNodeTests.cs +++ b/test/DynamoCoreTests/CodeBlockNodeTests.cs @@ -2270,9 +2270,22 @@ public void TestCompletionOnDerivedTypeReturnsBaseType() var codeCompletionServices = new CodeCompletionServices(libraryServicesCore); var completions = codeCompletionServices.GetCompletionsOnType("", "DupTargetTest").ToList(); Assert.AreEqual(3, completions.Count); - Assert.AreEqual("Foo", completions[0].Text); - Assert.AreEqual("DupTargetTest", completions[1].Text); + Assert.AreEqual("DupTargetTest", completions[0].Text); + Assert.AreEqual("Bar", completions[1].Text); + Assert.AreEqual("Foo", completions[2].Text); + } + + [Test] + [Category("UnitTests")] + public void TestCompletionOnDerivedTypeReturnsNonHiddenBaseMethods() + { + var codeCompletionServices = new CodeCompletionServices(libraryServicesCore); + var completions = codeCompletionServices.GetCompletionsOnType("", "FFITarget.HidesMethodFromClassA").ToList(); + Assert.AreEqual(4, completions.Count); + Assert.AreEqual("HidesMethodFromClassA", completions[0].Text); + Assert.AreEqual("Baz", completions[1].Text); Assert.AreEqual("Bar", completions[2].Text); + Assert.AreEqual("Foo", completions[3].Text); } [Test] diff --git a/test/Engine/ProtoTest/FFITests/CSFFITest.cs b/test/Engine/ProtoTest/FFITests/CSFFITest.cs index 1d7369e20f7..ca8cdfca217 100644 --- a/test/Engine/ProtoTest/FFITests/CSFFITest.cs +++ b/test/Engine/ProtoTest/FFITests/CSFFITest.cs @@ -501,6 +501,23 @@ public void TestInheritanceCtorsVirtualMethods() ExecuteAndVerify(code, data); } + [Test] + public void TestStaticHiddenFunctionCallResolution() + { + string code = @" + d = HidesMethodFromClassA.Baz(); + b = ClassA.Baz(); + "; + + Type dummy = typeof(FFITarget.HidesMethodFromClassA); + code = string.Format("import(\"{0}\");\r\n{1}", dummy.AssemblyQualifiedName, code); + Console.WriteLine(code); + ValidationData[] data = { new ValidationData { ValueName = "d", ExpectedValue = 23, BlockIndex = 0 }, + new ValidationData { ValueName = "b", ExpectedValue = 234, BlockIndex = 0 } + }; + ExecuteAndVerify(code, data); + } + [Test] public void TestInheritanceCtorsVirtualMethods2() {