diff --git a/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs b/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs
new file mode 100644
index 0000000000..e06c42490a
--- /dev/null
+++ b/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs
@@ -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
+{
+ ///
+ /// Statement reachability analysis.
+ ///
+ public sealed class ReachabilityAnalysis
+ {
+ HashSet reachableStatements = new HashSet();
+ HashSet reachableEndPoints = new HashSet();
+ HashSet visitedNodes = new HashSet();
+ Stack stack = new Stack();
+
+ 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 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);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
index 83836ca089..1765288c78 100644
--- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
+++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
@@ -60,6 +60,7 @@
+
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
index 756d22ba6a..47525c3053 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/CSharpAstResolver.cs
@@ -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;
+ }
+
///
/// Gets the type resolve context for the root resolver.
///
diff --git a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
index 59cf220389..31d441a07a 100644
--- a/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
+++ b/ICSharpCode.NRefactory.CSharp/Resolver/ResolveVisitor.cs
@@ -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
IList returnExpressions;
IList 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
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
{
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
IList returnExpressions;
IList 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
}
}
- 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
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
return SpecialType.UnknownType;
}
- void AnalyzeLambda(AstNode body, bool isAsync, out bool success, out bool isValidAsVoidMethod, out IType inferredReturnType, out IList returnExpressions, out IList returnValues)
+ void AnalyzeLambda(AstNode body, bool isAsync, out bool success, out bool isValidAsVoidMethod, out bool isEndpointUnreachable, out IType inferredReturnType, out IList returnExpressions, out IList returnValues)
{
+ isEndpointUnreachable = false;
Expression expr = body as Expression;
if (expr != null) {
isValidAsVoidMethod = ExpressionPermittedAsStatement(expr);
@@ -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
|| expr is AssignmentExpression;
}
- static bool IsValidLambda(bool isValidAsVoidMethod, bool isAsync, IList returnValues, IType returnType, Conversions conversions)
+ static bool IsValidLambda(bool isValidAsVoidMethod, bool isEndpointUnreachable, bool isAsync, IList 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
return isValidAsVoidMethod;
} else {
if (returnValues.Count == 0)
- return false;
+ return isEndpointUnreachable;
if (isAsync) {
// async lambdas must return Task
if (!(IsTask(returnType) && returnType.TypeParameterCount == 1))
diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
index 7ba7a5f597..6c5286cf67 100644
--- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
+++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/LambdaTests.cs
@@ -446,6 +446,28 @@ class Test {
Assert.IsTrue(c.IsValid);
}
+ [Test]
+ public void ThrowingAnonymousMethodIsConvertibleToFunc()
+ {
+ string program = @"using System;
+class Test {
+ Func x = $delegate { throw new NotImplementedException(); }$;
+}";
+ var c = GetConversion(program);
+ Assert.IsTrue(c.IsValid);
+ }
+
+ [Test]
+ public void EmptyAnonymousMethodIsNotConvertibleToFunc()
+ {
+ string program = @"using System;
+class Test {
+ Func x = $delegate { }$;
+}";
+ var c = GetConversion(program);
+ Assert.IsFalse(c.IsValid);
+ }
+
[Test]
public void RaisePropertyChanged_WithExpressionLambda()
{