Browse Source

Allow conversion of anonymous method with unreachable endpoint to Func<T>.

newNRvisualizers
Daniel Grunwald 14 years ago
parent
commit
1a7ca154b4
  1. 82
      ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs
  2. 1
      ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
  3. 6
      ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
  4. 22
      ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
  5. 22
      ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs

82
ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
// 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.Threading;
using ICSharpCode.NRefactory.CSharp.Resolver;
namespace ICSharpCode.NRefactory.CSharp.Analysis
{
/// <summary>
/// Statement reachability analysis.
/// </summary>
public sealed class ReachabilityAnalysis
{
HashSet<Statement> reachableStatements = new HashSet<Statement>();
HashSet<Statement> reachableEndPoints = new HashSet<Statement>();
HashSet<ControlFlowNode> visitedNodes = new HashSet<ControlFlowNode>();
Stack<ControlFlowNode> stack = new Stack<ControlFlowNode>();
private ReachabilityAnalysis() {}
public static ReachabilityAnalysis Create(Statement statement, CSharpAstResolver resolver = null, CancellationToken cancellationToken = default(CancellationToken))
{
var cfgBuilder = new ControlFlowGraphBuilder();
var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, cancellationToken);
return Create(cfg, cancellationToken);
}
public static ReachabilityAnalysis Create(IList<ControlFlowNode> controlFlowGraph, CancellationToken cancellationToken = default(CancellationToken))
{
if (controlFlowGraph == null)
throw new ArgumentNullException("controlFlowGraph");
ReachabilityAnalysis ra = new ReachabilityAnalysis();
ra.stack.Push(controlFlowGraph[0]);
while (ra.stack.Count > 0) {
cancellationToken.ThrowIfCancellationRequested();
ra.MarkReachable(ra.stack.Pop());
}
ra.stack = null;
ra.visitedNodes = null;
return ra;
}
void MarkReachable(ControlFlowNode node)
{
if (node.PreviousStatement != null)
reachableEndPoints.Add(node.PreviousStatement);
if (node.NextStatement != null)
reachableStatements.Add(node.NextStatement);
foreach (var edge in node.Outgoing) {
if (visitedNodes.Add(edge.To))
stack.Push(edge.To);
}
}
public bool IsReachable(Statement statement)
{
return reachableStatements.Contains(statement);
}
public bool IsEndpointReachable(Statement statement)
{
return reachableEndPoints.Contains(statement);
}
}
}

1
ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj

@ -60,6 +60,7 @@ @@ -60,6 +60,7 @@
<ItemGroup>
<Compile Include="Analysis\ControlFlow.cs" />
<Compile Include="Analysis\DefiniteAssignmentAnalysis.cs" />
<Compile Include="Analysis\ReachabilityAnalysis.cs" />
<Compile Include="Ast\AstNode.cs" />
<Compile Include="Ast\AstNodeCollection.cs" />
<Compile Include="Ast\AstType.cs" />

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

@ -78,6 +78,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -78,6 +78,12 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
this.resolveVisitor = new ResolveVisitor(initialResolverState, parsedFile);
}
internal CSharpAstResolver(ResolveVisitor resolveVisitor)
{
this.resolveVisitor = resolveVisitor;
this.resolverInitialized = true;
}
/// <summary>
/// Gets the type resolve context for the root resolver.
/// </summary>

22
ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs

@ -22,6 +22,7 @@ using System.Diagnostics; @@ -22,6 +22,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using ICSharpCode.NRefactory.CSharp.Analysis;
using ICSharpCode.NRefactory.CSharp.TypeSystem;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem;
@ -1759,6 +1760,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1759,6 +1760,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
IList<Expression> returnExpressions;
IList<ResolveResult> returnValues;
bool isValidAsVoidMethod;
bool isEndpointUnreachable;
bool success;
// The actual return type is set when the lambda is applied by the conversion.
@ -1820,7 +1822,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1820,7 +1822,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
delegate {
var oldNavigator = visitor.navigator;
visitor.navigator = new ConstantModeResolveVisitorNavigator(ResolveVisitorNavigationMode.Resolve, oldNavigator);
visitor.AnalyzeLambda(body, isAsync, out success, out isValidAsVoidMethod, out inferredReturnType, out returnExpressions, out returnValues);
visitor.AnalyzeLambda(body, isAsync, out success, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
visitor.navigator = oldNavigator;
});
Log.Unindent();
@ -1836,7 +1838,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -1836,7 +1838,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
{
Log.WriteLine("Testing validity of {0} for return-type {1}...", this, returnType);
Log.Indent();
bool valid = Analyze() && IsValidLambda(isValidAsVoidMethod, isAsync, returnValues, returnType, conversions);
bool valid = Analyze() && IsValidLambda(isValidAsVoidMethod, isEndpointUnreachable, isAsync, returnValues, returnType, conversions);
Log.Unindent();
Log.WriteLine("{0} is {1} for return-type {2}", this, valid ? "valid" : "invalid", returnType);
if (valid) {
@ -2103,6 +2105,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2103,6 +2105,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
IList<Expression> returnExpressions;
IList<ResolveResult> returnValues;
bool isValidAsVoidMethod;
bool isEndpointUnreachable;
internal bool success;
public LambdaTypeHypothesis(ImplicitlyTypedLambda lambda, IType[] parameterTypes, ResolveVisitor visitor,
@ -2135,7 +2138,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2135,7 +2138,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
}
}
visitor.AnalyzeLambda(lambda.BodyExpression, lambda.IsAsync, out success, out isValidAsVoidMethod, out inferredReturnType, out returnExpressions, out returnValues);
visitor.AnalyzeLambda(lambda.BodyExpression, lambda.IsAsync, out success, out isValidAsVoidMethod, out isEndpointUnreachable, out inferredReturnType, out returnExpressions, out returnValues);
visitor.resolver = oldResolver;
Log.Unindent();
Log.WriteLine("Finished analyzing " + ToString());
@ -2153,7 +2156,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2153,7 +2156,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
public Conversion IsValid(IType returnType, Conversions conversions)
{
if (success && IsValidLambda(isValidAsVoidMethod, lambda.IsAsync, returnValues, returnType, conversions)) {
if (success && IsValidLambda(isValidAsVoidMethod, isEndpointUnreachable, lambda.IsAsync, returnValues, returnType, conversions)) {
return new AnonymousFunctionConversion(returnType, this);
} else {
return Conversion.None;
@ -2285,8 +2288,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2285,8 +2288,9 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return SpecialType.UnknownType;
}
void AnalyzeLambda(AstNode body, bool isAsync, out bool success, out bool isValidAsVoidMethod, out IType inferredReturnType, out IList<Expression> returnExpressions, out IList<ResolveResult> returnValues)
void AnalyzeLambda(AstNode body, bool isAsync, out bool success, out bool isValidAsVoidMethod, out bool isEndpointUnreachable, out IType inferredReturnType, out IList<Expression> returnExpressions, out IList<ResolveResult> returnValues)
{
isEndpointUnreachable = false;
Expression expr = body as Expression;
if (expr != null) {
isValidAsVoidMethod = ExpressionPermittedAsStatement(expr);
@ -2314,6 +2318,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2314,6 +2318,10 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
inferredReturnType = ti.GetBestCommonType(returnValues, out tiSuccess);
// Failure to infer a return type does not make the lambda invalid,
// so we can ignore the 'tiSuccess' value
if (isValidAsVoidMethod && returnExpressions.Count == 0 && body is Statement) {
var reachabilityAnalysis = ReachabilityAnalysis.Create((Statement)body, new CSharpAstResolver(this), cancellationToken);
isEndpointUnreachable = !reachabilityAnalysis.IsEndpointReachable((Statement)body);
}
}
}
if (isAsync)
@ -2344,7 +2352,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2344,7 +2352,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
|| expr is AssignmentExpression;
}
static bool IsValidLambda(bool isValidAsVoidMethod, bool isAsync, IList<ResolveResult> returnValues, IType returnType, Conversions conversions)
static bool IsValidLambda(bool isValidAsVoidMethod, bool isEndpointUnreachable, bool isAsync, IList<ResolveResult> returnValues, IType returnType, Conversions conversions)
{
if (returnType.Kind == TypeKind.Void) {
// Lambdas that are valid statement lambdas or expression lambdas with a statement-expression
@ -2356,7 +2364,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver @@ -2356,7 +2364,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver
return isValidAsVoidMethod;
} else {
if (returnValues.Count == 0)
return false;
return isEndpointUnreachable;
if (isAsync) {
// async lambdas must return Task<T>
if (!(IsTask(returnType) && returnType.TypeParameterCount == 1))

22
ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs

@ -446,6 +446,28 @@ class Test { @@ -446,6 +446,28 @@ class Test {
Assert.IsTrue(c.IsValid);
}
[Test]
public void ThrowingAnonymousMethodIsConvertibleToFunc()
{
string program = @"using System;
class Test {
Func<string, int> x = $delegate { throw new NotImplementedException(); }$;
}";
var c = GetConversion(program);
Assert.IsTrue(c.IsValid);
}
[Test]
public void EmptyAnonymousMethodIsNotConvertibleToFunc()
{
string program = @"using System;
class Test {
Func<string, int> x = $delegate { }$;
}";
var c = GetConversion(program);
Assert.IsFalse(c.IsValid);
}
[Test]
public void RaisePropertyChanged_WithExpressionLambda()
{

Loading…
Cancel
Save