Browse Source

Implemented support for extension methods.

newNRvisualizers
Daniel Grunwald 15 years ago
parent
commit
f7b16a70fb
  1. 7
      ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs
  2. 2
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExtensionMethodTests.cs
  3. 10
      ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs
  4. 122
      ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs
  5. 6
      ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs
  6. 24
      ICSharpCode.NRefactory/CSharp/Resolver/MethodGroupResolveResult.cs
  7. 4
      ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs

7
ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeMembers/MethodDeclarationTests.cs

@ -250,6 +250,7 @@ namespace ICSharpCode.NRefactory.CSharp.Parser.TypeMembers @@ -250,6 +250,7 @@ namespace ICSharpCode.NRefactory.CSharp.Parser.TypeMembers
Assert.AreEqual("s", md.Parameters[0].ParameterName);
Assert.AreEqual("System.String", md.Parameters[0].TypeReference.Type);
}
*/
[Test]
public void VoidExtensionMethodTest()
@ -258,11 +259,13 @@ namespace ICSharpCode.NRefactory.CSharp.Parser.TypeMembers @@ -258,11 +259,13 @@ namespace ICSharpCode.NRefactory.CSharp.Parser.TypeMembers
"public static void Print(this string s) { Console.WriteLine(s); }"
);
Assert.AreEqual("Print", md.Name);
Assert.AreEqual("s", md.Parameters.First().Name);
Assert.AreEqual(ParameterModifier.This, md.Parameters.First().ParameterModifier);
Assert.AreEqual("string", ((PrimitiveType)md.Parameters.First().Type).Keyword);
Assert.IsTrue(md.IsExtensionMethod);
Assert.AreEqual("s", md.Parameters[0].ParameterName);
Assert.AreEqual("System.String", md.Parameters[0].TypeReference.Type);
}
/* TODO
[Test]
public void MethodWithEmptyAssignmentErrorInBody()
{

2
ICSharpCode.NRefactory.Tests/CSharp/Resolver/ExtensionMethodTests.cs

@ -7,7 +7,7 @@ using NUnit.Framework; @@ -7,7 +7,7 @@ using NUnit.Framework;
namespace ICSharpCode.NRefactory.CSharp.Resolver
{
[TestFixture, Ignore("Extension method support not yet implemented")]
[TestFixture]
public class ExtensionMethodTests : ResolverTestBase
{
[Test]

10
ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs

@ -1762,10 +1762,12 @@ namespace ICSharpCode.NRefactory.CSharp @@ -1762,10 +1762,12 @@ namespace ICSharpCode.NRefactory.CSharp
if (location != null)
parameterDeclarationExpression.AddChild (new CSharpTokenNode (Convert (location[0]), "params".Length), ParameterDeclaration.Roles.Keyword);
break;
case Parameter.Modifier.This:
parameterDeclarationExpression.ParameterModifier = ParameterModifier.This;
if (location != null)
parameterDeclarationExpression.AddChild (new CSharpTokenNode (Convert (location[0]), "this".Length), ParameterDeclaration.Roles.Keyword);
default:
if (p.HasExtensionMethodModifier) {
parameterDeclarationExpression.ParameterModifier = ParameterModifier.This;
if (location != null)
parameterDeclarationExpression.AddChild (new CSharpTokenNode (Convert (location[0]), "this".Length), ParameterDeclaration.Roles.Keyword);
}
break;
}
if (p.TypeExpression != null) // lambdas may have no types (a, b) => ...

122
ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs

@ -1688,7 +1688,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1688,7 +1688,16 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return DynamicResult;
MemberLookup lookup = CreateMemberLookup();
return lookup.Lookup(target.Type, identifier, typeArguments, isInvocationTarget);
ResolveResult result = lookup.Lookup(target.Type, identifier, typeArguments, isInvocationTarget);
if (result is UnknownMemberResolveResult) {
var extensionMethods = GetExtensionMethods(target.Type, identifier, typeArguments.Count);
if (extensionMethods.Count > 0) {
return new MethodGroupResolveResult(target.Type, identifier, EmptyList<IMethod>.Instance, typeArguments) {
ExtensionMethods = extensionMethods
};
}
}
return result;
}
MemberLookup CreateMemberLookup()
@ -1697,6 +1706,61 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1697,6 +1706,61 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
#endregion
#region GetExtensionMethods
/// <summary>
/// Gets the extension methods that are called 'name', and can be called with 'typeArgumentCount' explicit type arguments;
/// and are applicable with a first argument type of 'targetType'.
/// </summary>
List<List<IMethod>> GetExtensionMethods(IType targetType, string name, int typeArgumentCount)
{
List<List<IMethod>> extensionMethodGroups = new List<List<IMethod>>();
foreach (var inputGroup in GetAllExtensionMethods()) {
List<IMethod> outputGroup = new List<IMethod>();
foreach (var method in inputGroup) {
if (method.Name == name && (typeArgumentCount == 0 || method.TypeParameters.Count == typeArgumentCount)) {
// TODO: verify targetType
outputGroup.Add(method);
}
}
if (outputGroup.Count > 0)
extensionMethodGroups.Add(outputGroup);
}
return extensionMethodGroups;
}
List<List<IMethod>> GetAllExtensionMethods()
{
// TODO: maybe cache the result?
List<List<IMethod>> extensionMethodGroups = new List<List<IMethod>>();
List<IMethod> m;
for (UsingScope scope = this.UsingScope; scope != null; scope = scope.Parent) {
m = GetExtensionMethods(scope.NamespaceName).ToList();
if (m.Count > 0)
extensionMethodGroups.Add(m);
m = (
from u in scope.Usings
select u.ResolveNamespace(context) into ns
where ns != null
select ns.NamespaceName
).Distinct().SelectMany(ns => GetExtensionMethods(ns)).ToList();
if (m.Count > 0)
extensionMethodGroups.Add(m);
}
return extensionMethodGroups;
}
IEnumerable<IMethod> GetExtensionMethods(string namespaceName)
{
return
from c in context.GetClasses(namespaceName, StringComparer.Ordinal)
where c.IsStatic
from m in c.Methods
where (m.IsExtensionMethod)
select m;
}
#endregion
#region ResolveInvocation
public ResolveResult ResolveInvocation(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null)
{
@ -1709,15 +1773,57 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1709,15 +1773,57 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
MethodGroupResolveResult mgrr = target as MethodGroupResolveResult;
if (mgrr != null) {
OverloadResolution or = new OverloadResolution(context, arguments, argumentNames, mgrr.TypeArguments.ToArray());
var typeArgumentArray = mgrr.TypeArguments.ToArray();
OverloadResolution or = new OverloadResolution(context, arguments, argumentNames, typeArgumentArray);
foreach (IMethod method in mgrr.Methods) {
// TODO: grouping by class definition?
or.AddCandidate(method);
}
// TODO: extension methods?
IType returnType = or.BestCandidate.ReturnType.Resolve(context);
returnType = returnType.AcceptVisitor(new MethodTypeParameterSubstitution(or.InferredTypeArguments));
return new MemberResolveResult(or.BestCandidate, returnType);
if (!or.FoundApplicableCandidate) {
// No applicable match found, so let's try extension methods.
var extensionMethods = mgrr.ExtensionMethods;
// Look in extension methods pre-calcalculated by ResolveMemberAccess if possible;
// otherwise call GetExtensionMethods().
if (extensionMethods == null)
extensionMethods = GetExtensionMethods(mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments.Count);
if (extensionMethods.Count > 0) {
ResolveResult[] extArguments = new ResolveResult[arguments.Length + 1];
extArguments[0] = new ResolveResult(mgrr.TargetType);
arguments.CopyTo(extArguments, 1);
string[] extArgumentNames = null;
if (argumentNames != null) {
extArgumentNames = new string[argumentNames.Length + 1];
argumentNames.CopyTo(extArgumentNames, 1);
}
var extOr = new OverloadResolution(context, extArguments, extArgumentNames, typeArgumentArray);
foreach (var g in extensionMethods) {
foreach (var m in g) {
extOr.AddCandidate(m);
}
if (extOr.FoundApplicableCandidate)
break;
}
// For the lack of a better comparison function (the one within OverloadResolution
// cannot be used as it depends on the argument set):
if (extOr.FoundApplicableCandidate || or.BestCandidate == null) {
// Consider an extension method result better than the normal result only
// if it's applicable; or if there is no normal result.
or = extOr;
}
}
}
if (or.BestCandidate != null) {
IType returnType = or.BestCandidate.ReturnType.Resolve(context);
returnType = returnType.AcceptVisitor(new MethodTypeParameterSubstitution(or.InferredTypeArguments));
return new MemberResolveResult(or.BestCandidate, returnType);
} else {
// No candidate found at all (not even an inapplicable one).
// This can happen with empty method groups (as sometimes used with extension methods)
return new UnknownMethodResolveResult(mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames));
}
}
UnknownMemberResolveResult umrr = target as UnknownMemberResolveResult;
if (umrr != null) {
@ -1826,7 +1932,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1826,7 +1932,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
OverloadResolution or = new OverloadResolution(context, arguments, argumentNames, new IType[0]);
MemberLookup lookup = CreateMemberLookup();
bool allowProtectedAccess = lookup.AllowProtectedAccess(target.Type);
bool allowProtectedAccess = lookup.IsProtectedAccessAllowed(target.Type);
var indexers = target.Type.GetProperties(context, p => p.IsIndexer && lookup.IsAccessible(p, allowProtectedAccess));
// TODO: filter indexers hiding other indexers?
foreach (IProperty p in indexers) {
@ -1848,7 +1954,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1848,7 +1954,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
OverloadResolution or = new OverloadResolution(context, arguments, argumentNames, new IType[0]);
MemberLookup lookup = CreateMemberLookup();
bool allowProtectedAccess = lookup.AllowProtectedAccess(type);
bool allowProtectedAccess = lookup.IsProtectedAccessAllowed(type);
var constructors = type.GetConstructors(context, m => lookup.IsAccessible(m, allowProtectedAccess));
foreach (IMethod ctor in constructors) {
or.AddCandidate(ctor);

6
ICSharpCode.NRefactory/CSharp/Resolver/MemberLookup.cs

@ -45,7 +45,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -45,7 +45,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
#region IsAccessible
public bool AllowProtectedAccess(IType targetType)
public bool IsProtectedAccessAllowed(IType targetType)
{
ITypeDefinition typeDef = targetType.GetDefinition();
return typeDef != null && typeDef.IsDerivedFrom(currentTypeDefinition, context);
@ -164,7 +164,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -164,7 +164,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
types.AddRange(type.GetNestedTypes(context, typeFilter));
}
bool allowProtectedAccess = AllowProtectedAccess(type);
bool allowProtectedAccess = IsProtectedAccessAllowed(type);
if (typeArgumentCount == 0) {
Predicate<IMember> memberFilter = delegate(IMember member) {
@ -248,7 +248,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -248,7 +248,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
if (members.Count == 1 && firstNonMethod != null)
return new MemberResolveResult(firstNonMethod, context);
if (firstNonMethod == null)
return new MethodGroupResolveResult(members.ConvertAll(m => (IMethod)m), typeArguments);
return new MethodGroupResolveResult(type, name, members.ConvertAll(m => (IMethod)m), typeArguments);
return new AmbiguousMemberResultResult(firstNonMethod, firstNonMethod.ReturnType.Resolve(context));
}

24
ICSharpCode.NRefactory/CSharp/Resolver/MethodGroupResolveResult.cs

@ -16,13 +16,33 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -16,13 +16,33 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
{
readonly ReadOnlyCollection<IMethod> methods;
readonly ReadOnlyCollection<IType> typeArguments;
readonly IType targetType;
readonly string methodName;
public MethodGroupResolveResult(IList<IMethod> methods, IList<IType> typeArguments) : base(SharedTypes.UnknownType)
/// <summary>
/// List of extension methods, used to avoid re-calculating it in ResolveInvocation() when it was already
/// calculated by ResolveMemberAccess().
/// </summary>
internal List<List<IMethod>> ExtensionMethods;
public MethodGroupResolveResult(IType targetType, string methodName, IList<IMethod> methods, IList<IType> typeArguments) : base(SharedTypes.UnknownType)
{
if (targetType == null)
throw new ArgumentNullException("targetType");
if (methods == null)
throw new ArgumentNullException("methods");
this.targetType = targetType;
this.methodName = methodName;
this.methods = new ReadOnlyCollection<IMethod>(methods);
this.typeArguments = new ReadOnlyCollection<IType>(typeArguments);
this.typeArguments = typeArguments != null ? new ReadOnlyCollection<IType>(typeArguments) : EmptyList<IType>.Instance;
}
public IType TargetType {
get { return targetType; }
}
public string MethodName {
get { return methodName; }
}
public ReadOnlyCollection<IMethod> Methods {

4
ICSharpCode.NRefactory/CSharp/Resolver/OverloadResolution.cs

@ -522,6 +522,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -522,6 +522,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
}
public bool FoundApplicableCandidate {
get { return bestCandidate != null && bestCandidate.Errors == OverloadResolutionErrors.None; }
}
public IParameterizedMember BestCandidateAmbiguousWith {
get { return bestCandidateAmbiguousWith != null ? bestCandidateAmbiguousWith.Member : null; }
}

Loading…
Cancel
Save