diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs b/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs index 5af732ddb1..64582af816 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/MethodGroupResolveResult.cs @@ -100,8 +100,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } /// - /// Gets the methods that were found. + /// Gets the methods that were found, grouped by their declaring type. /// This list does not include extension methods. + /// Base types come first in the list. /// public IEnumerable MethodsGroupedByDeclaringType { get { return methodLists; } diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs index 7ce0e04064..9193307010 100644 --- a/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.NRefactory.CSharp/Resolver/OverloadResolution.cs @@ -235,6 +235,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver /// This method implements the logic that causes applicable methods in derived types to hide /// all methods in base types. /// + /// The methods, grouped by declaring type. Base types must come first in the list. public void AddMethodLists(IList methodLists) { if (methodLists == null) diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MemberLookupTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MemberLookupTests.cs new file mode 100644 index 0000000000..ce35506467 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/MemberLookupTests.cs @@ -0,0 +1,125 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; + +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + [TestFixture] + public class MemberLookupTests : ResolverTestBase + { + MemberLookup lookup; + + public override void SetUp() + { + base.SetUp(); + lookup = new MemberLookup(context, null, project); + } + + CSharpParsedFile Parse(string program) + { + CompilationUnit cu = new CSharpParser().Parse(new StringReader(program)); + CSharpParsedFile parsedFile = new TypeSystemConvertVisitor(project, "test.cs").Convert(cu); + project.UpdateProjectContent(null, parsedFile); + return parsedFile; + } + + [Test] + public void GroupMethodsByDeclaringType() + { + string program = @" +class Base { + public virtual void Method() {} +} +class Middle : Base { + public void Method(int p) {} +} +class Derived : Middle { + public override void Method() {} +}"; + ITypeDefinition derived = Parse(program).TopLevelTypeDefinitions[2]; + var rr = lookup.Lookup(new ResolveResult(derived), "Method", EmptyList.Instance, true) as MethodGroupResolveResult; + Assert.AreEqual(2, rr.MethodsGroupedByDeclaringType.Count()); + + var baseGroup = rr.MethodsGroupedByDeclaringType.ElementAt(0); + Assert.AreEqual("Base", baseGroup.DeclaringType.ReflectionName); + Assert.AreEqual(1, baseGroup.Count); + Assert.AreEqual("Derived.Method", baseGroup[0].FullName); + + var middleGroup = rr.MethodsGroupedByDeclaringType.ElementAt(1); + Assert.AreEqual("Middle", middleGroup.DeclaringType.ReflectionName); + Assert.AreEqual(1, middleGroup.Count); + Assert.AreEqual("Middle.Method", middleGroup[0].FullName); + } + + [Test] + public void MethodInGenericClassOverriddenByConcreteMethod() + { + string program = @" +class Base { + public virtual void Method(T a) {} +} +class Derived : Base { + public override void Method(int a) {} + public override void Method(string a) {} +}"; + ITypeDefinition derived = Parse(program).TopLevelTypeDefinitions[1]; + var rr = lookup.Lookup(new ResolveResult(derived), "Method", EmptyList.Instance, true) as MethodGroupResolveResult; + Assert.AreEqual(2, rr.MethodsGroupedByDeclaringType.Count()); + + var baseGroup = rr.MethodsGroupedByDeclaringType.ElementAt(0); + Assert.AreEqual("Base`1[[System.Int32]]", baseGroup.DeclaringType.ReflectionName); + Assert.AreEqual(1, baseGroup.Count); + Assert.AreEqual("Derived.Method", baseGroup[0].FullName); + Assert.AreEqual("System.Int32", baseGroup[0].Parameters[0].Type.Resolve(context).ReflectionName); + + var derivedGroup = rr.MethodsGroupedByDeclaringType.ElementAt(1); + Assert.AreEqual("Derived", derivedGroup.DeclaringType.ReflectionName); + Assert.AreEqual(1, derivedGroup.Count); + Assert.AreEqual("Derived.Method", derivedGroup[0].FullName); + Assert.AreEqual("System.String", derivedGroup[0].Parameters[0].Type.Resolve(context).ReflectionName); + } + + [Test] + public void GenericMethod() + { + string program = @" +class Base { + public virtual void Method(T a) {} +} +class Derived : Base { + public override void Method(S a) {} +}"; + ITypeDefinition derived = Parse(program).TopLevelTypeDefinitions[1]; + var rr = lookup.Lookup(new ResolveResult(derived), "Method", EmptyList.Instance, true) as MethodGroupResolveResult; + Assert.AreEqual(1, rr.MethodsGroupedByDeclaringType.Count()); + + var baseGroup = rr.MethodsGroupedByDeclaringType.ElementAt(0); + Assert.AreEqual("Base", baseGroup.DeclaringType.ReflectionName); + Assert.AreEqual(1, baseGroup.Count); + Assert.AreEqual("Derived.Method", baseGroup[0].FullName); + Assert.AreEqual("``0", baseGroup[0].Parameters[0].Type.Resolve(context).ReflectionName); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs index 2585e70ba1..c15aa4ae23 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs @@ -197,7 +197,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver SetUp(); CSharpParsedFile parsedFile = new CSharpParsedFile("test.cs", resolver.CurrentUsingScope); - TypeSystemConvertVisitor convertVisitor = new TypeSystemConvertVisitor(parsedFile, resolver.CurrentUsingScope, null); + TypeSystemConvertVisitor convertVisitor = new TypeSystemConvertVisitor(parsedFile); cu.AcceptVisitor(convertVisitor, null); project.UpdateProjectContent(null, convertVisitor.ParsedFile); diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index ad8a9fb826..5394410868 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -154,6 +154,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs index 8b74b7267d..98bec8dcea 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs @@ -431,8 +431,8 @@ namespace ICSharpCode.NRefactory.TypeSystem Assert.IsFalse(type.TypeParameters[0].HasReferenceTypeConstraint); Assert.IsTrue(type.TypeParameters[1].HasReferenceTypeConstraint); - Assert.IsTrue(type.TypeParameters[0].IsReferenceType(ctx) == true); - Assert.IsTrue(type.TypeParameters[1].IsReferenceType(ctx) == true); + Assert.IsNull(type.TypeParameters[0].IsReferenceType(ctx)); + Assert.AreEqual(true, type.TypeParameters[1].IsReferenceType(ctx)); } [Test] diff --git a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs index f1ed305f7a..2a98f1ccc9 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ExtensionMethods.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Note that this method does not return all supertypes - doing so is impossible due to contravariance /// (and undesirable for covariance as the list could become very large). /// - /// The output is ordered so that base types occur in before derived types. + /// The output is ordered so that base types occur before derived types. /// public static IEnumerable GetAllBaseTypes(this IType type, ITypeResolveContext context) { @@ -47,12 +47,12 @@ namespace ICSharpCode.NRefactory.TypeSystem } /// - /// Gets the non-interface base types. + /// Gets all non-interface base types. /// /// - /// When is an interface, this method will also return base interfaces. + /// When is an interface, this method will also return base interfaces (return same output as GetAllBaseTypes()). /// - /// The output is ordered so that base types occur in before derived types. + /// The output is ordered so that base types occur before derived types. /// public static IEnumerable GetNonInterfaceBaseTypes(this IType type, ITypeResolveContext context) { diff --git a/ICSharpCode.NRefactory/TypeSystem/ParameterListComparer.cs b/ICSharpCode.NRefactory/TypeSystem/ParameterListComparer.cs index 2a55c1e1fd..bf0698f339 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ParameterListComparer.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ParameterListComparer.cs @@ -18,11 +18,51 @@ using System; using System.Collections.Generic; +using System.Threading; +using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem { public static class ParameterListComparer { + // We want to consider the parameter lists "Method(T a)" and "Method(S b)" as equal. + // However, the parameter types are not considered equal, as T is a different type parameter than S. + // In order to compare the method signatures, we will normalize all method type parameters. + sealed class NormalizeMethodTypeParameters : TypeVisitor + { + public static readonly NormalizeMethodTypeParameters Instance = new NormalizeMethodTypeParameters(); + + ITypeParameter[] normalTypeParameters = { new DefaultTypeParameter(EntityType.Method, 0, string.Empty) }; + + public override IType VisitTypeParameter(ITypeParameter type) + { + if (type.OwnerType == EntityType.Method) { + ITypeParameter[] tps = this.normalTypeParameters; + while (type.Index >= tps.Length) { + // We don't have a normal type parameter for this index, so we need to extend our array. + // Because the array can be used concurrently from multiple threads, we have to use + // Interlocked.CompareExchange. + ITypeParameter[] newTps = new ITypeParameter[type.Index + 1]; + tps.CopyTo(newTps, 0); + for (int i = tps.Length; i < newTps.Length; i++) { + newTps[i] = new DefaultTypeParameter(EntityType.Method, i, string.Empty); + } + ITypeParameter[] oldTps = Interlocked.CompareExchange(ref normalTypeParameters, newTps, tps); + if (oldTps == tps) { + // exchange successful + tps = newTps; + } else { + // exchange not successful + tps = oldTps; + } + } + return tps[type.Index]; + } else { + return base.VisitTypeParameter(type); + } + } + } + public static bool Compare(ITypeResolveContext context, IParameterizedMember x, IParameterizedMember y) { var px = x.Parameters; @@ -36,7 +76,13 @@ namespace ICSharpCode.NRefactory.TypeSystem continue; if (a == null || b == null) return false; - if (!a.Type.Resolve(context).Equals(b.Type.Resolve(context))) + IType aType = a.Type.Resolve(context); + IType bType = b.Type.Resolve(context); + + aType = aType.AcceptVisitor(NormalizeMethodTypeParameters.Instance); + bType = bType.AcceptVisitor(NormalizeMethodTypeParameters.Instance); + + if (!aType.Equals(bType)) return false; } return true; @@ -48,7 +94,9 @@ namespace ICSharpCode.NRefactory.TypeSystem unchecked { foreach (IParameter p in obj.Parameters) { hashCode *= 27; - hashCode += p.Type.Resolve(context).GetHashCode(); + IType type = p.Type.Resolve(context); + type = type.AcceptVisitor(NormalizeMethodTypeParameters.Instance); + hashCode += type.GetHashCode(); } } return hashCode;