Skip to content
This repository has been archived by the owner on Oct 16, 2020. It is now read-only.

Commit

Permalink
Merge branch 'await' of git://github.com/erik-kallen/NRefactory
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrunwald committed Oct 4, 2012
2 parents 47a4701 + 8d5536e commit 46c6a00
Show file tree
Hide file tree
Showing 6 changed files with 583 additions and 9 deletions.
Expand Up @@ -337,6 +337,7 @@
<Compile Include="Refactoring\TypeSystemAstBuilder.cs" />
<Compile Include="Refactoring\VariableReferenceGraph.cs" />
<Compile Include="Resolver\CompositeResolveVisitorNavigator.cs" />
<Compile Include="Resolver\AwaitResolveResult.cs" />
<Compile Include="Resolver\DynamicInvocationResolveResult.cs" />
<Compile Include="Resolver\DynamicMemberResolveResult.cs" />
<Compile Include="Resolver\CSharpConversions.cs" />
Expand Down
80 changes: 80 additions & 0 deletions ICSharpCode.NRefactory.CSharp/Resolver/AwaitResolveResult.cs
@@ -0,0 +1,80 @@
// 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.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem;

namespace ICSharpCode.NRefactory.CSharp.Resolver
{
/// <summary>
/// Represents the result of an await expression.
/// </summary>
public class AwaitResolveResult : ResolveResult
{
/// <summary>
/// The method representing the GetAwaiter() call. Can be null if the GetAwaiter method was not found.
/// </summary>
public readonly ResolveResult GetAwaiterInvocation;

/// <summary>
/// Awaiter type. Will not be null (but can be UnknownType).
/// </summary>
public readonly IType AwaiterType;

/// <summary>
/// Property representing the IsCompleted property on the awaiter type. Can be null if the awaiter type or the property was not found, or when awaiting a dynamic expression.
/// </summary>
public readonly IProperty IsCompletedProperty;

/// <summary>
/// Method representing the OnCompleted method on the awaiter type. Can be null if the awaiter type or the method was not found, or when awaiting a dynamic expression.
/// </summary>
public readonly IMethod OnCompletedMethod;

/// <summary>
/// Method representing the GetResult method on the awaiter type. Can be null if the awaiter type or the method was not found, or when awaiting a dynamic expression.
/// </summary>
public readonly IMethod GetResultMethod;

public AwaitResolveResult(IType resultType, ResolveResult getAwaiterInvocation, IType awaiterType, IProperty isCompletedProperty, IMethod onCompletedMethod, IMethod getResultMethod)
: base(resultType)
{
if (awaiterType == null)
throw new ArgumentNullException("awaiterType");
if (getAwaiterInvocation == null)
throw new ArgumentNullException("getAwaiterInvocation");
this.GetAwaiterInvocation = getAwaiterInvocation;
this.AwaiterType = awaiterType;
this.IsCompletedProperty = isCompletedProperty;
this.OnCompletedMethod = onCompletedMethod;
this.GetResultMethod = getResultMethod;
}

public override bool IsError {
get { return this.GetAwaiterInvocation.IsError || (AwaiterType.Kind != TypeKind.Dynamic && (this.IsCompletedProperty == null || this.OnCompletedMethod == null || this.GetResultMethod == null)); }
}

public override IEnumerable<ResolveResult> GetChildResults() {
return new[] { GetAwaiterInvocation };
}
}
}
44 changes: 35 additions & 9 deletions ICSharpCode.NRefactory.CSharp/Resolver/CSharpResolver.cs
Expand Up @@ -360,8 +360,14 @@ public CSharpResolver Clone()
#region ResolveUnaryOperator method
public ResolveResult ResolveUnaryOperator(UnaryOperatorType op, ResolveResult expression)
{
if (expression.Type.Kind == TypeKind.Dynamic)
return UnaryOperatorResolveResult(SpecialType.Dynamic, op, expression);
if (expression.Type.Kind == TypeKind.Dynamic) {
if (op == UnaryOperatorType.Await) {
return new AwaitResolveResult(SpecialType.Dynamic, new DynamicInvocationResolveResult(new DynamicMemberResolveResult(expression, "GetAwaiter"), DynamicInvocationType.Invocation, EmptyList<ResolveResult>.Instance), SpecialType.Dynamic, null, null, null);
}
else {
return UnaryOperatorResolveResult(SpecialType.Dynamic, op, expression);
}
}

// C# 4.0 spec: §7.3.3 Unary operator overload resolution
string overloadableOperatorName = GetOverloadableOperatorName(op);
Expand All @@ -375,17 +381,37 @@ public ResolveResult ResolveUnaryOperator(UnaryOperatorType op, ResolveResult ex
return ErrorResult;
case UnaryOperatorType.AddressOf:
return UnaryOperatorResolveResult(new PointerType(expression.Type), op, expression);
case UnaryOperatorType.Await:
case UnaryOperatorType.Await: {
ResolveResult getAwaiterMethodGroup = ResolveMemberAccess(expression, "GetAwaiter", EmptyList<IType>.Instance, NameLookupMode.InvocationTarget);
ResolveResult getAwaiterInvocation = ResolveInvocation(getAwaiterMethodGroup, new ResolveResult[0]);
var getResultMethodGroup = CreateMemberLookup().Lookup(getAwaiterInvocation, "GetResult", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;

var lookup = CreateMemberLookup();
IMethod getResultMethod;
IType awaitResultType;
var getResultMethodGroup = lookup.Lookup(getAwaiterInvocation, "GetResult", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;
if (getResultMethodGroup != null) {
var or = getResultMethodGroup.PerformOverloadResolution(compilation, new ResolveResult[0], allowExtensionMethods: false, conversions: conversions);
IType awaitResultType = or.GetBestCandidateWithSubstitutedTypeArguments().ReturnType;
return UnaryOperatorResolveResult(awaitResultType, UnaryOperatorType.Await, expression);
} else {
return UnaryOperatorResolveResult(SpecialType.UnknownType, UnaryOperatorType.Await, expression);
var getResultOR = getResultMethodGroup.PerformOverloadResolution(compilation, new ResolveResult[0], allowExtensionMethods: false, conversions: conversions);
getResultMethod = getResultOR.FoundApplicableCandidate ? getResultOR.GetBestCandidateWithSubstitutedTypeArguments() as IMethod : null;
awaitResultType = getResultMethod != null ? getResultMethod.ReturnType : SpecialType.UnknownType;
}
else {
getResultMethod = null;
awaitResultType = SpecialType.UnknownType;
}

var isCompletedRR = lookup.Lookup(getAwaiterInvocation, "IsCompleted", EmptyList<IType>.Instance, false);
var isCompletedProperty = (isCompletedRR is MemberResolveResult ? ((MemberResolveResult)isCompletedRR).Member as IProperty : null);

var onCompletedMethodGroup = lookup.Lookup(getAwaiterInvocation, "OnCompleted", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;
IMethod onCompletedMethod = null;
if (onCompletedMethodGroup != null) {
var onCompletedOR = onCompletedMethodGroup.PerformOverloadResolution(compilation, new ResolveResult[] { new TypeResolveResult(compilation.FindType(new FullTypeName("System.Action"))) }, allowExtensionMethods: false, conversions: conversions);
onCompletedMethod = (onCompletedOR.FoundApplicableCandidate ? onCompletedOR.GetBestCandidateWithSubstitutedTypeArguments() as IMethod : null);
}

return new AwaitResolveResult(awaitResultType, getAwaiterInvocation, getAwaiterInvocation.Type, isCompletedProperty, onCompletedMethod, getResultMethod);
}

default:
throw new ArgumentException("Invalid value for UnaryOperatorType", "op");
}
Expand Down
43 changes: 43 additions & 0 deletions ICSharpCode.NRefactory.CSharp/Resolver/FindReferences.cs
Expand Up @@ -244,6 +244,8 @@ public IList<IFindReferenceSearchScope> GetSearchScopes(IEntity entity)
scope = FindMemberReferences(entity, m => new FindPropertyReferences((IProperty)m));
if (entity.Name == "Current")
additionalScope = FindEnumeratorCurrentReferences((IProperty)entity);
else if (entity.Name == "IsCompleted")
additionalScope = FindAwaiterIsCompletedReferences((IProperty)entity);
break;
case EntityType.Event:
scope = FindMemberReferences(entity, m => new FindEventReferences((IEvent)m));
Expand Down Expand Up @@ -661,6 +663,15 @@ SearchScope FindEnumeratorCurrentReferences(IProperty property)
return imported != null ? new FindEnumeratorCurrentReferencesNavigator(imported) : null;
});
}

SearchScope FindAwaiterIsCompletedReferences(IProperty property)
{
return new SearchScope(
delegate(ICompilation compilation) {
IProperty imported = compilation.Import(property);
return imported != null ? new FindAwaiterIsCompletedReferencesNavigator(imported) : null;
});
}

sealed class FindEnumeratorCurrentReferencesNavigator : FindReferenceNavigator
{
Expand All @@ -682,6 +693,27 @@ internal override bool IsMatch(ResolveResult rr)
return ferr != null && ferr.CurrentProperty != null && findReferences.IsMemberMatch(property, ferr.CurrentProperty, true);
}
}

sealed class FindAwaiterIsCompletedReferencesNavigator : FindReferenceNavigator
{
IProperty property;

public FindAwaiterIsCompletedReferencesNavigator(IProperty property)
{
this.property = property;
}

internal override bool CanMatch(AstNode node)
{
return node is UnaryOperatorExpression;
}

internal override bool IsMatch(ResolveResult rr)
{
AwaitResolveResult arr = rr as AwaitResolveResult;
return arr != null && arr.IsCompletedProperty != null && findReferences.IsMemberMatch(property, arr.IsCompletedProperty, true);
}
}
#endregion

#region Find Method References
Expand Down Expand Up @@ -724,6 +756,11 @@ SearchScope GetSearchScopeForMethod(IMethod method)
case "MoveNext":
specialNodeType = typeof(ForeachStatement);
break;
case "GetAwaiter":
case "GetResult":
case "OnCompleted":
specialNodeType = typeof(UnaryOperatorExpression);
break;
default:
specialNodeType = null;
break;
Expand Down Expand Up @@ -794,6 +831,12 @@ internal override bool IsMatch(ResolveResult rr)
return IsMatch(ferr.GetEnumeratorCall)
|| (ferr.MoveNextMethod != null && findReferences.IsMemberMatch(method, ferr.MoveNextMethod, true));
}
var arr = rr as AwaitResolveResult;
if (arr != null) {
return IsMatch(arr.GetAwaiterInvocation)
|| (arr.GetResultMethod != null && findReferences.IsMemberMatch(method, arr.GetResultMethod, true))
|| (arr.OnCompletedMethod != null && findReferences.IsMemberMatch(method, arr.OnCompletedMethod, true));
}
}
var mrr = rr as MemberResolveResult;
return mrr != null && findReferences.IsMemberMatch(method, mrr.Member, mrr.IsVirtualCall);
Expand Down
58 changes: 58 additions & 0 deletions ICSharpCode.NRefactory.Tests/CSharp/Resolver/FindReferencesTest.cs
Expand Up @@ -211,5 +211,63 @@ public void InheritanceTest3()
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 9 && r is InvocationExpression));
}
#endregion

#region Await
const string awaitTest = @"using System;
class MyAwaiter {
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation) {}
public int GetResult() { return 0; }
}
class MyAwaitable {
public MyAwaiter GetAwaiter() { return null; }
}
public class C {
public async void M() {
MyAwaitable x = null;
int i = await x;
}
}";

[Test]
public void GetAwaiterReferenceInAwaitExpressionIsFound() {
Init(awaitTest);
var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "MyAwaitable");
var method = test.Methods.Single(m => m.Name == "GetAwaiter");
var actual = FindReferences(method).ToList();
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 8 && r is MethodDeclaration));
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 13 && r is UnaryOperatorExpression));
}

[Test]
public void GetResultReferenceInAwaitExpressionIsFound() {
Init(awaitTest);
var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "MyAwaiter");
var method = test.Methods.Single(m => m.Name == "GetResult");
var actual = FindReferences(method).ToList();
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 5 && r is MethodDeclaration));
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 13 && r is UnaryOperatorExpression));
}

[Test]
public void OnCompletedReferenceInAwaitExpressionIsFound() {
Init(awaitTest);
var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "MyAwaiter");
var method = test.Methods.Single(m => m.Name == "OnCompleted");
var actual = FindReferences(method).ToList();
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 4 && r is MethodDeclaration));
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 13 && r is UnaryOperatorExpression));
}

[Test]
public void IsCompletedReferenceInAwaitExpressionIsFound() {
Init(awaitTest);
var test = compilation.MainAssembly.TopLevelTypeDefinitions.Single(t => t.Name == "MyAwaiter");
var property = test.Properties.Single(m => m.Name == "IsCompleted");
var actual = FindReferences(property).ToList();
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 3 && r is PropertyDeclaration));
Assert.IsTrue(actual.Any(r => r.StartLocation.Line == 13 && r is UnaryOperatorExpression));
}
#endregion
}
}

0 comments on commit 46c6a00

Please sign in to comment.