diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 4af789de8..e71d3486d 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -41,13 +41,19 @@
False
TRACE
+
+ bin\NuGet-$(Configuration)\
+
-
+
..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.dll
-
+
..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.CSharp.dll
+
+ ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll
+
3.5
@@ -130,19 +136,26 @@
-
-
+
{D68133BD-1E63-496E-9EDE-4FBDBF77B486}
Mono.Cecil
+
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}
+ ICSharpCode.NRefactory.CSharp
+
+
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}
+ ICSharpCode.NRefactory
+
-
+
diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
index 808290a49..8bb4934e7 100644
--- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -49,12 +49,6 @@
..\..\packages\DiffLib.1.0.0.55\lib\net35-Client\DiffLib.dll
-
- ..\..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.dll
-
-
- ..\..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.CSharp.dll
-
False
.\nunit.framework.dll
@@ -121,6 +115,14 @@
{D68133BD-1E63-496E-9EDE-4FBDBF77B486}
Mono.Cecil
+
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}
+ ICSharpCode.NRefactory.CSharp
+
+
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}
+ ICSharpCode.NRefactory
+
{984CC812-9470-4A13-AFF9-CC44068D666C}
ICSharpCode.Decompiler
diff --git a/ICSharpCode.Decompiler/Tests/packages.config b/ICSharpCode.Decompiler/Tests/packages.config
index b6522ccdb..4d992b424 100644
--- a/ICSharpCode.Decompiler/Tests/packages.config
+++ b/ICSharpCode.Decompiler/Tests/packages.config
@@ -1,6 +1,4 @@
-
-
\ No newline at end of file
diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj
index 2e507061a..7c56424cb 100644
--- a/ILSpy.AddIn/ILSpy.AddIn.csproj
+++ b/ILSpy.AddIn/ILSpy.AddIn.csproj
@@ -57,12 +57,6 @@
False
False
-
- ..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.dll
-
-
- ..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.CSharp.dll
-
False
@@ -220,10 +214,18 @@
{63e6915c-7ea4-4d76-ab28-0d7191eea626}
Mono.Cecil.Pdb
+
+ {53dca265-3c3c-42f9-b647-f72ba678122b}
+ ICSharpCode.NRefactory.CSharp
+
{7b82b671-419f-45f4-b778-d9286f996efa}
ICSharpCode.NRefactory.VB
+
+ {3b2a5653-ec97-4001-bb9b-d90f1af2c371}
+ ICSharpCode.NRefactory
+
{dde2a481-8271-4eac-a330-8fa6a38d13d1}
ICSharpCode.TreeView
diff --git a/ILSpy.AddIn/packages.config b/ILSpy.AddIn/packages.config
index 5f793afd7..14a098365 100644
--- a/ILSpy.AddIn/packages.config
+++ b/ILSpy.AddIn/packages.config
@@ -1,7 +1,5 @@
-
-
diff --git a/ILSpy.sln b/ILSpy.sln
index 0d57ce0ae..7a69bb14f 100644
--- a/ILSpy.sln
+++ b/ILSpy.sln
@@ -1,7 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
-# SharpDevelop 5.1
+# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{F45DB999-7E72-4000-B5AD-3A7B485A0896}"
@@ -19,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.AvalonEdit", "A
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler", "ICSharpCode.Decompiler\ICSharpCode.Decompiler.csproj", "{984CC812-9470-4A13-AFF9-CC44068D666C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory", "NRefactory\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj", "{3B2A5653-EC97-4001-BB9B-D90F1AF2C371}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler.Tests", "ICSharpCode.Decompiler\Tests\ICSharpCode.Decompiler.Tests.csproj", "{FEC0DA52-C4A6-4710-BE36-B484A20C5E22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "TestPlugin\TestPlugin.csproj", "{F32EBCC8-0E53-4421-867E-05B3D6E10C70}"
@@ -31,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy.BamlDecompiler", "ILS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy.BamlDecompiler.Tests", "ILSpy.BamlDecompiler\Tests\ILSpy.BamlDecompiler.Tests.csproj", "{1169E6D1-1899-43D4-A500-07CE4235B388}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.CSharp", "NRefactory\ICSharpCode.NRefactory.CSharp\ICSharpCode.NRefactory.CSharp.csproj", "{53DCA265-3C3C-42F9-B647-F72BA678122B}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy.AddIn", "ILSpy.AddIn\ILSpy.AddIn.csproj", "{9D7BE6C0-B7B3-4A50-A54E-18A2D84A3384}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A344E19-D1FC-4F4C-8883-0844AC669113}"
@@ -86,6 +89,14 @@ Global
{984CC812-9470-4A13-AFF9-CC44068D666C}.Release|Any CPU.Build.0 = Release|Any CPU
{984CC812-9470-4A13-AFF9-CC44068D666C}.Release|x86.ActiveCfg = Release|Any CPU
{984CC812-9470-4A13-AFF9-CC44068D666C}.Release|x86.Build.0 = Release|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|x86.Build.0 = Debug|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|x86.ActiveCfg = Release|Any CPU
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|x86.Build.0 = Release|Any CPU
{FEC0DA52-C4A6-4710-BE36-B484A20C5E22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FEC0DA52-C4A6-4710-BE36-B484A20C5E22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FEC0DA52-C4A6-4710-BE36-B484A20C5E22}.Debug|x86.ActiveCfg = Debug|x86
@@ -134,6 +145,14 @@ Global
{1169E6D1-1899-43D4-A500-07CE4235B388}.Release|Any CPU.Build.0 = Release|Any CPU
{1169E6D1-1899-43D4-A500-07CE4235B388}.Release|x86.ActiveCfg = Release|x86
{1169E6D1-1899-43D4-A500-07CE4235B388}.Release|x86.Build.0 = Release|x86
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|x86.Build.0 = Debug|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|x86.ActiveCfg = Release|Any CPU
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|x86.Build.0 = Release|Any CPU
{9D7BE6C0-B7B3-4A50-A54E-18A2D84A3384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D7BE6C0-B7B3-4A50-A54E-18A2D84A3384}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D7BE6C0-B7B3-4A50-A54E-18A2D84A3384}.Debug|x86.ActiveCfg = Debug|Any CPU
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index 44c274807..46a29db40 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -55,15 +55,6 @@
TRACE
-
- ..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.dll
-
-
- ..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.Cecil.dll
-
-
- ..\packages\ICSharpCode.NRefactory.5.5.1\lib\Net40\ICSharpCode.NRefactory.CSharp.dll
-
3.0
@@ -262,7 +253,6 @@
-
@@ -392,10 +382,18 @@
{63E6915C-7EA4-4D76-AB28-0D7191EEA626}
Mono.Cecil.Pdb
+
+ {53DCA265-3C3C-42F9-B647-F72BA678122B}
+ ICSharpCode.NRefactory.CSharp
+
{7B82B671-419F-45F4-B778-D9286F996EFA}
ICSharpCode.NRefactory.VB
+
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}
+ ICSharpCode.NRefactory
+
{DDE2A481-8271-4EAC-A330-8FA6A38D13D1}
ICSharpCode.TreeView
diff --git a/ILSpy/VB/ILSpyEnvironmentProvider.cs b/ILSpy/VB/ILSpyEnvironmentProvider.cs
index 279000d78..78c46da12 100644
--- a/ILSpy/VB/ILSpyEnvironmentProvider.cs
+++ b/ILSpy/VB/ILSpyEnvironmentProvider.cs
@@ -36,7 +36,7 @@ namespace ICSharpCode.ILSpy.VB
}
}
- readonly CecilLoader loader = new CecilLoader();
+ //readonly CecilLoader loader = new CecilLoader();
public string GetTypeNameForAttribute(ICSharpCode.NRefactory.CSharp.Attribute attribute)
{
diff --git a/ILSpy/packages.config b/ILSpy/packages.config
deleted file mode 100644
index ade7de76c..000000000
--- a/ILSpy/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/NRefactory/.gitattributes b/NRefactory/.gitattributes
index 1a1533024..fbfb41be8 100644
--- a/NRefactory/.gitattributes
+++ b/NRefactory/.gitattributes
@@ -1,2 +1,3 @@
-*.sln -crlf
-*.csproj -crlf
\ No newline at end of file
+*.cs text diff=csharp
+*.sln text eol=crlf
+*.csproj text eol=crlf
diff --git a/NRefactory/.gitignore b/NRefactory/.gitignore
index 0031dcbe2..813e7d15c 100644
--- a/NRefactory/.gitignore
+++ b/NRefactory/.gitignore
@@ -1,5 +1,6 @@
bin
obj
+*.suo
/lib/*.dll
/ICSharpCode.NRefactory.Tests/PartCover/*
_ReSharper*/*
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/AnnotationNames.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/AnnotationNames.cs
new file mode 100644
index 000000000..94d19e4b6
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/AnnotationNames.cs
@@ -0,0 +1,48 @@
+//
+// Annotations.cs
+//
+// Author:
+// Luís Reis
+//
+// Copyright (c) 2013 Luís Reis
+//
+// 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;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ public static class AnnotationNames
+ {
+ //Used const instead of readonly to allow values to be used in switch cases.
+
+ public const string AssertionMethodAttribute = "JetBrains.Annotations.AssertionMethodAttribute";
+ public const string AssertionConditionAttribute = "JetBrains.Annotations.AssertionConditionAttribute";
+ public const string AssertionConditionTypeAttribute = "JetBrains.Annotations.AssertionConditionType";
+
+ public const string AssertionConditionTypeIsTrue = "JetBrains.Annotations.AssertionConditionType.IS_TRUE";
+ public const string AssertionConditionTypeIsFalse = "JetBrains.Annotations.AssertionConditionType.IS_FALSE";
+ public const string AssertionConditionTypeIsNull = "JetBrains.Annotations.AssertionConditionType.IS_NULL";
+ public const string AssertionConditionTypeIsNotNull = "JetBrains.Annotations.AssertionConditionType.IS_NOT_NULL";
+
+ public const string NotNullAttribute = "JetBrains.Annotations.NotNullAttribute";
+ public const string CanBeNullAttribute = "JetBrains.Annotations.CanBeNullAttribute";
+ }
+}
+
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ControlFlow.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ControlFlow.cs
new file mode 100644
index 000000000..2d3895b1e
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ControlFlow.cs
@@ -0,0 +1,803 @@
+// Copyright (c) 2010-2013 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.Diagnostics;
+using System.Linq;
+using System.Threading;
+using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.CSharp.TypeSystem;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.NRefactory.TypeSystem.Implementation;
+using ICSharpCode.NRefactory.Utils;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ ///
+ /// Represents a node in the control flow graph of a C# method.
+ ///
+ public class ControlFlowNode
+ {
+ public readonly Statement PreviousStatement;
+ public readonly Statement NextStatement;
+
+ public readonly ControlFlowNodeType Type;
+
+ public readonly List Outgoing = new List();
+ public readonly List Incoming = new List();
+
+ public ControlFlowNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ {
+ if (previousStatement == null && nextStatement == null)
+ throw new ArgumentException("previousStatement and nextStatement must not be both null");
+ this.PreviousStatement = previousStatement;
+ this.NextStatement = nextStatement;
+ this.Type = type;
+ }
+ }
+
+ public enum ControlFlowNodeType
+ {
+ ///
+ /// Unknown node type
+ ///
+ None,
+ ///
+ /// Node in front of a statement
+ ///
+ StartNode,
+ ///
+ /// Node between two statements
+ ///
+ BetweenStatements,
+ ///
+ /// Node at the end of a statement list
+ ///
+ EndNode,
+ ///
+ /// Node representing the position before evaluating the condition of a loop.
+ ///
+ LoopCondition
+ }
+
+ public class ControlFlowEdge
+ {
+ public readonly ControlFlowNode From;
+ public readonly ControlFlowNode To;
+ public readonly ControlFlowEdgeType Type;
+
+ List jumpOutOfTryFinally;
+
+ public ControlFlowEdge(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type)
+ {
+ if (from == null)
+ throw new ArgumentNullException("from");
+ if (to == null)
+ throw new ArgumentNullException("to");
+ this.From = from;
+ this.To = to;
+ this.Type = type;
+ }
+
+ internal void AddJumpOutOfTryFinally(TryCatchStatement tryFinally)
+ {
+ if (jumpOutOfTryFinally == null)
+ jumpOutOfTryFinally = new List();
+ jumpOutOfTryFinally.Add(tryFinally);
+ }
+
+ ///
+ /// Gets whether this control flow edge is leaving any try-finally statements.
+ ///
+ public bool IsLeavingTryFinally {
+ get { return jumpOutOfTryFinally != null; }
+ }
+
+ ///
+ /// Gets the try-finally statements that this control flow edge is leaving.
+ ///
+ public IEnumerable TryFinallyStatements {
+ get { return jumpOutOfTryFinally ?? Enumerable.Empty(); }
+ }
+ }
+
+ public enum ControlFlowEdgeType
+ {
+ ///
+ /// Regular control flow.
+ ///
+ Normal,
+ ///
+ /// Conditional control flow (edge taken if condition is true)
+ ///
+ ConditionTrue,
+ ///
+ /// Conditional control flow (edge taken if condition is false)
+ ///
+ ConditionFalse,
+ ///
+ /// A jump statement (goto, goto case, break or continue)
+ ///
+ Jump
+ }
+
+ ///
+ /// Constructs the control flow graph for C# statements.
+ ///
+ public class ControlFlowGraphBuilder
+ {
+ // Written according to the reachability rules in the C# spec (§8.1 End points and reachability)
+
+ protected virtual ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new ControlFlowNode(previousStatement, nextStatement, type);
+ }
+
+ protected virtual ControlFlowEdge CreateEdge(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new ControlFlowEdge(from, to, type);
+ }
+
+ Statement rootStatement;
+ CSharpTypeResolveContext typeResolveContext;
+ Func resolver;
+ List nodes;
+ Dictionary labels;
+ List gotoStatements;
+ CancellationToken cancellationToken;
+
+ public IList BuildControlFlowGraph(Statement statement, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (statement == null)
+ throw new ArgumentNullException("statement");
+ CSharpResolver r = new CSharpResolver(MinimalCorlib.Instance.CreateCompilation());
+ return BuildControlFlowGraph(statement, new CSharpAstResolver(r, statement), cancellationToken);
+ }
+
+ public IList BuildControlFlowGraph(Statement statement, CSharpAstResolver resolver, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (statement == null)
+ throw new ArgumentNullException("statement");
+ if (resolver == null)
+ throw new ArgumentNullException("resolver");
+ return BuildControlFlowGraph(statement, resolver.Resolve, resolver.TypeResolveContext, cancellationToken);
+ }
+
+ internal IList BuildControlFlowGraph(Statement statement, Func resolver, CSharpTypeResolveContext typeResolveContext, CancellationToken cancellationToken)
+ {
+ NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor();
+ nodeCreationVisitor.builder = this;
+ try {
+ this.nodes = new List();
+ this.labels = new Dictionary();
+ this.gotoStatements = new List();
+ this.rootStatement = statement;
+ this.resolver = resolver;
+ this.typeResolveContext = typeResolveContext;
+ this.cancellationToken = cancellationToken;
+
+ ControlFlowNode entryPoint = CreateStartNode(statement);
+ statement.AcceptVisitor(nodeCreationVisitor, entryPoint);
+
+ // Resolve goto statements:
+ foreach (ControlFlowNode gotoStmt in gotoStatements) {
+ string label = ((GotoStatement)gotoStmt.NextStatement).Label;
+ ControlFlowNode labelNode;
+ if (labels.TryGetValue(label, out labelNode))
+ nodeCreationVisitor.Connect(gotoStmt, labelNode, ControlFlowEdgeType.Jump);
+ }
+
+ AnnotateLeaveEdgesWithTryFinallyBlocks();
+
+ return nodes;
+ } finally {
+ this.nodes = null;
+ this.labels = null;
+ this.gotoStatements = null;
+ this.rootStatement = null;
+ this.resolver = null;
+ this.typeResolveContext = null;
+ this.cancellationToken = CancellationToken.None;
+ }
+ }
+
+ void AnnotateLeaveEdgesWithTryFinallyBlocks()
+ {
+ foreach (ControlFlowEdge edge in nodes.SelectMany(n => n.Outgoing)) {
+ if (edge.Type != ControlFlowEdgeType.Jump) {
+ // Only jumps are potential candidates for leaving try-finally blocks.
+ // Note that the regular edges leaving try or catch blocks are already annotated by the visitor.
+ continue;
+ }
+ Statement gotoStatement = edge.From.NextStatement;
+ Debug.Assert(gotoStatement is GotoStatement || gotoStatement is GotoDefaultStatement || gotoStatement is GotoCaseStatement || gotoStatement is BreakStatement || gotoStatement is ContinueStatement);
+ Statement targetStatement = edge.To.PreviousStatement ?? edge.To.NextStatement;
+ if (gotoStatement.Parent == targetStatement.Parent)
+ continue;
+ HashSet targetParentTryCatch = new HashSet(targetStatement.Ancestors.OfType());
+ for (AstNode node = gotoStatement.Parent; node != null; node = node.Parent) {
+ TryCatchStatement leftTryCatch = node as TryCatchStatement;
+ if (leftTryCatch != null) {
+ if (targetParentTryCatch.Contains(leftTryCatch))
+ break;
+ if (!leftTryCatch.FinallyBlock.IsNull)
+ edge.AddJumpOutOfTryFinally(leftTryCatch);
+ }
+ }
+ }
+ }
+
+ #region Create*Node
+ ControlFlowNode CreateStartNode(Statement statement)
+ {
+ if (statement.IsNull)
+ return null;
+ ControlFlowNode node = CreateNode(null, statement, ControlFlowNodeType.StartNode);
+ nodes.Add(node);
+ return node;
+ }
+
+ ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type, bool addToNodeList = true)
+ {
+ ControlFlowNode node = CreateNode(null, statement, type);
+ if (addToNodeList)
+ nodes.Add(node);
+ return node;
+ }
+
+ ControlFlowNode CreateEndNode(Statement statement, bool addToNodeList = true)
+ {
+ Statement nextStatement;
+ if (statement == rootStatement) {
+ nextStatement = null;
+ } else {
+ // Find the next statement in the same role:
+ AstNode next = statement;
+ do {
+ next = next.NextSibling;
+ } while (next != null && next.Role != statement.Role);
+ nextStatement = next as Statement;
+ }
+ ControlFlowNodeType type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode;
+ ControlFlowNode node = CreateNode(statement, nextStatement, type);
+ if (addToNodeList)
+ nodes.Add(node);
+ return node;
+ }
+ #endregion
+
+ #region Constant evaluation
+ ///
+ /// Gets/Sets whether to handle only primitive expressions as constants (no complex expressions like "a + b").
+ ///
+ public bool EvaluateOnlyPrimitiveConstants { get; set; }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The constant value of the expression; or null if the expression is not a constant.
+ ResolveResult EvaluateConstant(Expression expr)
+ {
+ if (expr.IsNull)
+ return null;
+ if (EvaluateOnlyPrimitiveConstants) {
+ if (!(expr is PrimitiveExpression || expr is NullReferenceExpression))
+ return null;
+ }
+ return resolver(expr, cancellationToken);
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The value of the constant boolean expression; or null if the value is not a constant boolean expression.
+ bool? EvaluateCondition(Expression expr)
+ {
+ ResolveResult rr = EvaluateConstant(expr);
+ if (rr != null && rr.IsCompileTimeConstant)
+ return rr.ConstantValue as bool?;
+ else
+ return null;
+ }
+
+ bool AreEqualConstants(ResolveResult c1, ResolveResult c2)
+ {
+ if (c1 == null || c2 == null || !c1.IsCompileTimeConstant || !c2.IsCompileTimeConstant)
+ return false;
+ CSharpResolver r = new CSharpResolver(typeResolveContext);
+ ResolveResult c = r.ResolveBinaryOperator(BinaryOperatorType.Equality, c1, c2);
+ return c.IsCompileTimeConstant && (c.ConstantValue as bool?) == true;
+ }
+ #endregion
+
+ sealed class NodeCreationVisitor : DepthFirstAstVisitor
+ {
+ // 'data' parameter: input control flow node (start of statement being visited)
+ // Return value: result control flow node (end of statement being visited)
+
+ internal ControlFlowGraphBuilder builder;
+ Stack breakTargets = new Stack();
+ Stack continueTargets = new Stack();
+ List gotoCaseOrDefault = new List();
+
+ internal ControlFlowEdge Connect(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type = ControlFlowEdgeType.Normal)
+ {
+ if (from == null || to == null)
+ return null;
+ ControlFlowEdge edge = builder.CreateEdge(from, to, type);
+ from.Outgoing.Add(edge);
+ to.Incoming.Add(edge);
+ return edge;
+ }
+
+ ///
+ /// Creates an end node for stmt and connects from with the new node.
+ ///
+ ControlFlowNode CreateConnectedEndNode(Statement stmt, ControlFlowNode from)
+ {
+ ControlFlowNode newNode = builder.CreateEndNode(stmt);
+ Connect(from, newNode);
+ return newNode;
+ }
+
+ protected override ControlFlowNode VisitChildren(AstNode node, ControlFlowNode data)
+ {
+ // We have overrides for all possible statements and should visit statements only.
+ throw new NotSupportedException();
+ }
+
+ public override ControlFlowNode VisitBlockStatement(BlockStatement blockStatement, ControlFlowNode data)
+ {
+ // C# 4.0 spec: §8.2 Blocks
+ ControlFlowNode childNode = HandleStatementList(blockStatement.Statements, data);
+ return CreateConnectedEndNode(blockStatement, childNode);
+ }
+
+ ControlFlowNode HandleStatementList(AstNodeCollection statements, ControlFlowNode source)
+ {
+ ControlFlowNode childNode = null;
+ foreach (Statement stmt in statements) {
+ if (childNode == null) {
+ childNode = builder.CreateStartNode(stmt);
+ if (source != null)
+ Connect(source, childNode);
+ }
+ Debug.Assert(childNode.NextStatement == stmt);
+ childNode = stmt.AcceptVisitor(this, childNode);
+ Debug.Assert(childNode.PreviousStatement == stmt);
+ }
+ return childNode ?? source;
+ }
+
+ public override ControlFlowNode VisitEmptyStatement(EmptyStatement emptyStatement, ControlFlowNode data)
+ {
+ return CreateConnectedEndNode(emptyStatement, data);
+ }
+
+ public override ControlFlowNode VisitLabelStatement(LabelStatement labelStatement, ControlFlowNode data)
+ {
+ ControlFlowNode end = CreateConnectedEndNode(labelStatement, data);
+ builder.labels[labelStatement.Label] = end;
+ return end;
+ }
+
+ public override ControlFlowNode VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, ControlFlowNode data)
+ {
+ return CreateConnectedEndNode(variableDeclarationStatement, data);
+ }
+
+ public override ControlFlowNode VisitExpressionStatement(ExpressionStatement expressionStatement, ControlFlowNode data)
+ {
+ return CreateConnectedEndNode(expressionStatement, data);
+ }
+
+ public override ControlFlowNode VisitIfElseStatement(IfElseStatement ifElseStatement, ControlFlowNode data)
+ {
+ bool? cond = builder.EvaluateCondition(ifElseStatement.Condition);
+
+ ControlFlowNode trueBegin = builder.CreateStartNode(ifElseStatement.TrueStatement);
+ if (cond != false)
+ Connect(data, trueBegin, ControlFlowEdgeType.ConditionTrue);
+ ControlFlowNode trueEnd = ifElseStatement.TrueStatement.AcceptVisitor(this, trueBegin);
+
+ ControlFlowNode falseBegin = builder.CreateStartNode(ifElseStatement.FalseStatement);
+ if (cond != true)
+ Connect(data, falseBegin, ControlFlowEdgeType.ConditionFalse);
+ ControlFlowNode falseEnd = ifElseStatement.FalseStatement.AcceptVisitor(this, falseBegin);
+ // (if no else statement exists, both falseBegin and falseEnd will be null)
+
+ ControlFlowNode end = builder.CreateEndNode(ifElseStatement);
+ Connect(trueEnd, end);
+ if (falseEnd != null) {
+ Connect(falseEnd, end);
+ } else if (cond != true) {
+ Connect(data, end, ControlFlowEdgeType.ConditionFalse);
+ }
+ return end;
+ }
+
+ public override ControlFlowNode VisitSwitchStatement(SwitchStatement switchStatement, ControlFlowNode data)
+ {
+ // First, figure out which switch section will get called (if the expression is constant):
+ ResolveResult constant = builder.EvaluateConstant(switchStatement.Expression);
+ SwitchSection defaultSection = null;
+ SwitchSection sectionMatchedByConstant = null;
+ foreach (SwitchSection section in switchStatement.SwitchSections) {
+ foreach (CaseLabel label in section.CaseLabels) {
+ if (label.Expression.IsNull) {
+ defaultSection = section;
+ } else if (constant != null && constant.IsCompileTimeConstant) {
+ ResolveResult labelConstant = builder.EvaluateConstant(label.Expression);
+ if (builder.AreEqualConstants(constant, labelConstant))
+ sectionMatchedByConstant = section;
+ }
+ }
+ }
+ if (constant != null && constant.IsCompileTimeConstant && sectionMatchedByConstant == null)
+ sectionMatchedByConstant = defaultSection;
+
+ int gotoCaseOrDefaultInOuterScope = gotoCaseOrDefault.Count;
+ List sectionStartNodes = new List();
+
+ ControlFlowNode end = builder.CreateEndNode(switchStatement, addToNodeList: false);
+ breakTargets.Push(end);
+ foreach (SwitchSection section in switchStatement.SwitchSections) {
+ int sectionStartNodeID = builder.nodes.Count;
+ if (constant == null || !constant.IsCompileTimeConstant || section == sectionMatchedByConstant) {
+ HandleStatementList(section.Statements, data);
+ } else {
+ // This section is unreachable: pass null to HandleStatementList.
+ HandleStatementList(section.Statements, null);
+ }
+ // Don't bother connecting the ends of the sections: the 'break' statement takes care of that.
+
+ // Store the section start node for 'goto case' statements.
+ sectionStartNodes.Add(sectionStartNodeID < builder.nodes.Count ? builder.nodes[sectionStartNodeID] : null);
+ }
+ breakTargets.Pop();
+ if (defaultSection == null && sectionMatchedByConstant == null) {
+ Connect(data, end);
+ }
+
+ if (gotoCaseOrDefault.Count > gotoCaseOrDefaultInOuterScope) {
+ // Resolve 'goto case' statements:
+ for (int i = gotoCaseOrDefaultInOuterScope; i < gotoCaseOrDefault.Count; i++) {
+ ControlFlowNode gotoCaseNode = gotoCaseOrDefault[i];
+ GotoCaseStatement gotoCaseStatement = gotoCaseNode.NextStatement as GotoCaseStatement;
+ ResolveResult gotoCaseConstant = null;
+ if (gotoCaseStatement != null) {
+ gotoCaseConstant = builder.EvaluateConstant(gotoCaseStatement.LabelExpression);
+ }
+ int targetSectionIndex = -1;
+ int currentSectionIndex = 0;
+ foreach (SwitchSection section in switchStatement.SwitchSections) {
+ foreach (CaseLabel label in section.CaseLabels) {
+ if (gotoCaseStatement != null) {
+ // goto case
+ if (!label.Expression.IsNull) {
+ ResolveResult labelConstant = builder.EvaluateConstant(label.Expression);
+ if (builder.AreEqualConstants(gotoCaseConstant, labelConstant))
+ targetSectionIndex = currentSectionIndex;
+ }
+ } else {
+ // goto default
+ if (label.Expression.IsNull)
+ targetSectionIndex = currentSectionIndex;
+ }
+ }
+ currentSectionIndex++;
+ }
+ if (targetSectionIndex >= 0 && sectionStartNodes[targetSectionIndex] != null)
+ Connect(gotoCaseNode, sectionStartNodes[targetSectionIndex], ControlFlowEdgeType.Jump);
+ else
+ Connect(gotoCaseNode, end, ControlFlowEdgeType.Jump);
+ }
+ gotoCaseOrDefault.RemoveRange(gotoCaseOrDefaultInOuterScope, gotoCaseOrDefault.Count - gotoCaseOrDefaultInOuterScope);
+ }
+
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ public override ControlFlowNode VisitGotoCaseStatement(GotoCaseStatement gotoCaseStatement, ControlFlowNode data)
+ {
+ gotoCaseOrDefault.Add(data);
+ return builder.CreateEndNode(gotoCaseStatement);
+ }
+
+ public override ControlFlowNode VisitGotoDefaultStatement(GotoDefaultStatement gotoDefaultStatement, ControlFlowNode data)
+ {
+ gotoCaseOrDefault.Add(data);
+ return builder.CreateEndNode(gotoDefaultStatement);
+ }
+
+ public override ControlFlowNode VisitWhileStatement(WhileStatement whileStatement, ControlFlowNode data)
+ {
+ // while (cond) { embeddedStmt; }
+ ControlFlowNode end = builder.CreateEndNode(whileStatement, addToNodeList: false);
+ ControlFlowNode conditionNode = builder.CreateSpecialNode(whileStatement, ControlFlowNodeType.LoopCondition);
+ breakTargets.Push(end);
+ continueTargets.Push(conditionNode);
+
+ Connect(data, conditionNode);
+
+ bool? cond = builder.EvaluateCondition(whileStatement.Condition);
+ ControlFlowNode bodyStart = builder.CreateStartNode(whileStatement.EmbeddedStatement);
+ if (cond != false)
+ Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue);
+ ControlFlowNode bodyEnd = whileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart);
+ Connect(bodyEnd, conditionNode);
+ if (cond != true)
+ Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse);
+
+ breakTargets.Pop();
+ continueTargets.Pop();
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ public override ControlFlowNode VisitDoWhileStatement(DoWhileStatement doWhileStatement, ControlFlowNode data)
+ {
+ // do { embeddedStmt; } while(cond);
+ ControlFlowNode end = builder.CreateEndNode(doWhileStatement, addToNodeList: false);
+ ControlFlowNode conditionNode = builder.CreateSpecialNode(doWhileStatement, ControlFlowNodeType.LoopCondition, addToNodeList: false);
+ breakTargets.Push(end);
+ continueTargets.Push(conditionNode);
+
+ ControlFlowNode bodyStart = builder.CreateStartNode(doWhileStatement.EmbeddedStatement);
+ Connect(data, bodyStart);
+ ControlFlowNode bodyEnd = doWhileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart);
+ Connect(bodyEnd, conditionNode);
+
+ bool? cond = builder.EvaluateCondition(doWhileStatement.Condition);
+ if (cond != false)
+ Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue);
+ if (cond != true)
+ Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse);
+
+ breakTargets.Pop();
+ continueTargets.Pop();
+ builder.nodes.Add(conditionNode);
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ public override ControlFlowNode VisitForStatement(ForStatement forStatement, ControlFlowNode data)
+ {
+ data = HandleStatementList(forStatement.Initializers, data);
+ // for (initializers ; cond; iterators) { embeddedStmt; }
+ ControlFlowNode end = builder.CreateEndNode(forStatement, addToNodeList: false);
+ ControlFlowNode conditionNode = builder.CreateSpecialNode(forStatement, ControlFlowNodeType.LoopCondition);
+ Connect(data, conditionNode);
+
+ int iteratorStartNodeID = builder.nodes.Count;
+ ControlFlowNode iteratorEnd = HandleStatementList(forStatement.Iterators, null);
+ ControlFlowNode iteratorStart;
+ if (iteratorEnd != null) {
+ iteratorStart = builder.nodes[iteratorStartNodeID];
+ Connect(iteratorEnd, conditionNode);
+ } else {
+ iteratorStart = conditionNode;
+ }
+
+ breakTargets.Push(end);
+ continueTargets.Push(iteratorStart);
+
+ ControlFlowNode bodyStart = builder.CreateStartNode(forStatement.EmbeddedStatement);
+ ControlFlowNode bodyEnd = forStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart);
+ Connect(bodyEnd, iteratorStart);
+
+ breakTargets.Pop();
+ continueTargets.Pop();
+
+ bool? cond = forStatement.Condition.IsNull ? true : builder.EvaluateCondition(forStatement.Condition);
+ if (cond != false)
+ Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue);
+ if (cond != true)
+ Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse);
+
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ ControlFlowNode HandleEmbeddedStatement(Statement embeddedStatement, ControlFlowNode source)
+ {
+ if (embeddedStatement == null || embeddedStatement.IsNull)
+ return source;
+ ControlFlowNode bodyStart = builder.CreateStartNode(embeddedStatement);
+ if (source != null)
+ Connect(source, bodyStart);
+ return embeddedStatement.AcceptVisitor(this, bodyStart);
+ }
+
+ public override ControlFlowNode VisitForeachStatement(ForeachStatement foreachStatement, ControlFlowNode data)
+ {
+ // foreach (...) { embeddedStmt }
+ ControlFlowNode end = builder.CreateEndNode(foreachStatement, addToNodeList: false);
+ ControlFlowNode conditionNode = builder.CreateSpecialNode(foreachStatement, ControlFlowNodeType.LoopCondition);
+ Connect(data, conditionNode);
+
+ breakTargets.Push(end);
+ continueTargets.Push(conditionNode);
+
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(foreachStatement.EmbeddedStatement, conditionNode);
+ Connect(bodyEnd, conditionNode);
+
+ breakTargets.Pop();
+ continueTargets.Pop();
+
+ Connect(conditionNode, end);
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ public override ControlFlowNode VisitBreakStatement(BreakStatement breakStatement, ControlFlowNode data)
+ {
+ if (breakTargets.Count > 0)
+ Connect(data, breakTargets.Peek(), ControlFlowEdgeType.Jump);
+ return builder.CreateEndNode(breakStatement);
+ }
+
+ public override ControlFlowNode VisitContinueStatement(ContinueStatement continueStatement, ControlFlowNode data)
+ {
+ if (continueTargets.Count > 0)
+ Connect(data, continueTargets.Peek(), ControlFlowEdgeType.Jump);
+ return builder.CreateEndNode(continueStatement);
+ }
+
+ public override ControlFlowNode VisitGotoStatement(GotoStatement gotoStatement, ControlFlowNode data)
+ {
+ builder.gotoStatements.Add(data);
+ return builder.CreateEndNode(gotoStatement);
+ }
+
+ public override ControlFlowNode VisitReturnStatement(ReturnStatement returnStatement, ControlFlowNode data)
+ {
+ return builder.CreateEndNode(returnStatement); // end not connected with data
+ }
+
+ public override ControlFlowNode VisitThrowStatement(ThrowStatement throwStatement, ControlFlowNode data)
+ {
+ return builder.CreateEndNode(throwStatement); // end not connected with data
+ }
+
+ public override ControlFlowNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, ControlFlowNode data)
+ {
+ ControlFlowNode end = builder.CreateEndNode(tryCatchStatement, addToNodeList: false);
+ var edge = Connect(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data), end);
+ if (!tryCatchStatement.FinallyBlock.IsNull)
+ edge.AddJumpOutOfTryFinally(tryCatchStatement);
+ foreach (CatchClause cc in tryCatchStatement.CatchClauses) {
+ edge = Connect(HandleEmbeddedStatement(cc.Body, data), end);
+ if (!tryCatchStatement.FinallyBlock.IsNull)
+ edge.AddJumpOutOfTryFinally(tryCatchStatement);
+ }
+ if (!tryCatchStatement.FinallyBlock.IsNull) {
+ // Don't connect the end of the try-finally block to anything.
+ // Consumers of the CFG will have to special-case try-finally.
+ HandleEmbeddedStatement(tryCatchStatement.FinallyBlock, data);
+ }
+ builder.nodes.Add(end);
+ return end;
+ }
+
+ public override ControlFlowNode VisitCheckedStatement(CheckedStatement checkedStatement, ControlFlowNode data)
+ {
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(checkedStatement.Body, data);
+ return CreateConnectedEndNode(checkedStatement, bodyEnd);
+ }
+
+ public override ControlFlowNode VisitUncheckedStatement(UncheckedStatement uncheckedStatement, ControlFlowNode data)
+ {
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(uncheckedStatement.Body, data);
+ return CreateConnectedEndNode(uncheckedStatement, bodyEnd);
+ }
+
+ public override ControlFlowNode VisitLockStatement(LockStatement lockStatement, ControlFlowNode data)
+ {
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(lockStatement.EmbeddedStatement, data);
+ return CreateConnectedEndNode(lockStatement, bodyEnd);
+ }
+
+ public override ControlFlowNode VisitUsingStatement(UsingStatement usingStatement, ControlFlowNode data)
+ {
+ data = HandleEmbeddedStatement(usingStatement.ResourceAcquisition as Statement, data);
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(usingStatement.EmbeddedStatement, data);
+ return CreateConnectedEndNode(usingStatement, bodyEnd);
+ }
+
+ public override ControlFlowNode VisitYieldReturnStatement(YieldReturnStatement yieldStatement, ControlFlowNode data)
+ {
+ return CreateConnectedEndNode(yieldStatement, data);
+ }
+
+ public override ControlFlowNode VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, ControlFlowNode data)
+ {
+ return builder.CreateEndNode(yieldBreakStatement); // end not connected with data
+ }
+
+ public override ControlFlowNode VisitUnsafeStatement(UnsafeStatement unsafeStatement, ControlFlowNode data)
+ {
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(unsafeStatement.Body, data);
+ return CreateConnectedEndNode(unsafeStatement, bodyEnd);
+ }
+
+ public override ControlFlowNode VisitFixedStatement(FixedStatement fixedStatement, ControlFlowNode data)
+ {
+ ControlFlowNode bodyEnd = HandleEmbeddedStatement(fixedStatement.EmbeddedStatement, data);
+ return CreateConnectedEndNode(fixedStatement, bodyEnd);
+ }
+ }
+
+ ///
+ /// Debugging helper that exports a control flow graph.
+ ///
+ public static GraphVizGraph ExportGraph(IList nodes)
+ {
+ GraphVizGraph g = new GraphVizGraph();
+ GraphVizNode[] n = new GraphVizNode[nodes.Count];
+ Dictionary dict = new Dictionary();
+ for (int i = 0; i < n.Length; i++) {
+ dict.Add(nodes[i], i);
+ n[i] = new GraphVizNode(i);
+ string name = "#" + i + " = ";
+ switch (nodes[i].Type) {
+ case ControlFlowNodeType.StartNode:
+ case ControlFlowNodeType.BetweenStatements:
+ name += nodes[i].NextStatement.DebugToString();
+ break;
+ case ControlFlowNodeType.EndNode:
+ name += "End of " + nodes[i].PreviousStatement.DebugToString();
+ break;
+ case ControlFlowNodeType.LoopCondition:
+ name += "Condition in " + nodes[i].NextStatement.DebugToString();
+ break;
+ default:
+ name += "?";
+ break;
+ }
+ n[i].label = name;
+ g.AddNode(n[i]);
+ }
+ for (int i = 0; i < n.Length; i++) {
+ foreach (ControlFlowEdge edge in nodes[i].Outgoing) {
+ GraphVizEdge ge = new GraphVizEdge(i, dict[edge.To]);
+ if (edge.IsLeavingTryFinally)
+ ge.style = "dashed";
+ switch (edge.Type) {
+ case ControlFlowEdgeType.ConditionTrue:
+ ge.color = "green";
+ break;
+ case ControlFlowEdgeType.ConditionFalse:
+ ge.color = "red";
+ break;
+ case ControlFlowEdgeType.Jump:
+ ge.color = "blue";
+ break;
+ }
+ g.AddEdge(ge);
+ }
+ }
+ return g;
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpace.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpace.cs
new file mode 100644
index 000000000..a412cba3a
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpace.cs
@@ -0,0 +1,157 @@
+//
+// LovalVariableDeclarationSpace.cs
+//
+// Author:
+// Simon Lindgren
+//
+// Copyright (c) 2013 Simon Lindgren
+//
+// 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 ICSharpCode.NRefactory.Utils;
+using System.Collections.Generic;
+using System.Linq;
+using System;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ ///
+ /// Represents a declaration space. (§3.3)
+ ///
+ public class LocalDeclarationSpace
+ {
+ ///
+ /// Maps from variable name to the declarations in this declaration space.
+ ///
+ ///
+ /// This maps from variable name
+ ///
+ MultiDictionary declarations = new MultiDictionary ();
+
+ public LocalDeclarationSpace()
+ {
+ Children = new List ();
+ }
+
+ ///
+ /// The child declaration spaces.
+ ///
+ public IList Children {
+ get;
+ private set;
+ }
+
+ ///
+ /// The parent declaration space.
+ ///
+ /// The parent.
+ public LocalDeclarationSpace Parent {
+ get;
+ private set;
+ }
+
+ ///
+ /// The names declared in this declaration space, excluding child spaces.
+ ///
+ /// The declared names.
+ public ICollection DeclaredNames {
+ get {
+ return declarations.Keys;
+ }
+ }
+
+ ///
+ /// Get all nodes declaring the name specified in .
+ ///
+ /// The declaring nodes.
+ /// The declaration name.
+ public IEnumerable GetNameDeclarations(string name)
+ {
+ return declarations [name].Concat(Children.SelectMany(child => child.GetNameDeclarations(name)));
+ }
+
+ ///
+ /// Adds a child declaration space.
+ ///
+ /// The to add.
+ public void AddChildSpace(LocalDeclarationSpace child)
+ {
+ if (child == null)
+ throw new ArgumentNullException("child");
+ if (Children.Contains(child))
+ throw new InvalidOperationException("the child was already added");
+
+ Children.Add(child);
+ child.Parent = this;
+ }
+
+ ///
+ /// Adds a new declaration to the declaration space.
+ ///
+ /// The name of the declared variable.
+ /// A node associated with the declaration.
+ public void AddDeclaration(string name, AstNode node)
+ {
+ if (name == null)
+ throw new ArgumentNullException("name");
+ if (node == null)
+ throw new ArgumentNullException("node");
+ declarations.Add(name, node);
+ }
+
+ ///
+ /// Determines if the name exists in the this declaration space.
+ ///
+ /// true, if the name specified in is used in this variable declaration space, false otherwise.
+ /// The name to look for.
+ /// When true, child declaration spaces are included in the search.
+ public bool ContainsName(string name, bool includeChildren)
+ {
+ if (name == null)
+ throw new ArgumentNullException("name");
+
+ if (declarations.Keys.Contains(name))
+ return true;
+ return includeChildren && Children.Any(child => child.ContainsName(name, true));
+ }
+
+ ///
+ /// Determines whether the name specified in is used in surrouding code.
+ ///
+ /// true if the name is used, false otherwise.
+ /// The name to check.
+ ///
+ /// Contrary to , this method also checks parent declaration spaces
+ /// for name conflicts. Typically, this will be the right method to use when determining if a name can be used.
+ ///
+ public bool IsNameUsed(string name)
+ {
+ if (name == null)
+ throw new ArgumentNullException("name");
+
+ return IsNameUsedBySelfOrParent(name) || Children.Any(child => child.ContainsName(name, true));
+ }
+
+ bool IsNameUsedBySelfOrParent(string name)
+ {
+ if (declarations.Keys.Contains(name))
+ return true;
+ return Parent != null && Parent.IsNameUsedBySelfOrParent(name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpaceVisitor.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpaceVisitor.cs
new file mode 100644
index 000000000..08389a413
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DeclarationSpace/LocalDeclarationSpaceVisitor.cs
@@ -0,0 +1,138 @@
+//
+// LocalDeclarationSpaceVisitor.cs
+//
+// Author:
+// Simon Lindgren
+//
+// Copyright (c) 2013 Simon Lindgren
+//
+// 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;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ public class LocalDeclarationSpaceVisitor : DepthFirstAstVisitor
+ {
+ LocalDeclarationSpace currentDeclarationSpace;
+ Dictionary nodeDeclarationSpaces = new Dictionary();
+
+ public LocalDeclarationSpace GetDeclarationSpace(AstNode node)
+ {
+ if (node == null)
+ throw new ArgumentNullException("node");
+ while (node != null) {
+ LocalDeclarationSpace declarationSpace;
+ if (nodeDeclarationSpaces.TryGetValue(node, out declarationSpace))
+ return declarationSpace;
+ node = node.Parent;
+ }
+ return null;
+ }
+
+ #region Visitor
+
+ void AddDeclaration(string name, AstNode node)
+ {
+ if (currentDeclarationSpace != null)
+ currentDeclarationSpace.AddDeclaration(name, node);
+ }
+
+ public override void VisitVariableInitializer(VariableInitializer variableInitializer)
+ {
+ AddDeclaration(variableInitializer.Name, variableInitializer);
+ base.VisitVariableInitializer(variableInitializer);
+ }
+
+ public override void VisitParameterDeclaration(ParameterDeclaration parameterDeclaration)
+ {
+ AddDeclaration(parameterDeclaration.Name, parameterDeclaration);
+ base.VisitParameterDeclaration(parameterDeclaration);
+ }
+
+ void VisitNewDeclarationSpace(AstNode node)
+ {
+ var oldDeclarationSpace = currentDeclarationSpace;
+ currentDeclarationSpace = new LocalDeclarationSpace();
+ if (oldDeclarationSpace != null)
+ oldDeclarationSpace.AddChildSpace(currentDeclarationSpace);
+
+ VisitChildren(node);
+
+ nodeDeclarationSpaces.Add(node, currentDeclarationSpace);
+ currentDeclarationSpace = oldDeclarationSpace;
+ }
+
+ #region Declaration space creating nodes
+
+ public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration)
+ {
+ VisitNewDeclarationSpace(methodDeclaration);
+ }
+
+ public override void VisitBlockStatement(BlockStatement blockStatement)
+ {
+ VisitNewDeclarationSpace(blockStatement);
+ }
+
+ public override void VisitSwitchStatement(SwitchStatement switchStatement)
+ {
+ VisitNewDeclarationSpace(switchStatement);
+ }
+
+ public override void VisitForeachStatement(ForeachStatement foreachStatement)
+ {
+ AddDeclaration(foreachStatement.VariableName, foreachStatement);
+ VisitNewDeclarationSpace(foreachStatement);
+ }
+
+ public override void VisitForStatement(ForStatement forStatement)
+ {
+ VisitNewDeclarationSpace(forStatement);
+ }
+
+ public override void VisitUsingStatement(UsingStatement usingStatement)
+ {
+ VisitNewDeclarationSpace(usingStatement);
+ }
+
+ public override void VisitLambdaExpression(LambdaExpression lambdaExpression)
+ {
+ VisitNewDeclarationSpace(lambdaExpression);
+ }
+
+ public override void VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression)
+ {
+ VisitNewDeclarationSpace(anonymousMethodExpression);
+ }
+
+ public override void VisitEventDeclaration(EventDeclaration eventDeclaration)
+ {
+ AddDeclaration(eventDeclaration.Name, eventDeclaration);
+ }
+
+ public override void VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration)
+ {
+ VisitNewDeclarationSpace(eventDeclaration);
+ }
+
+ #endregion
+ #endregion
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DefiniteAssignmentAnalysis.cs
new file mode 100644
index 000000000..9b33e74a7
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DefiniteAssignmentAnalysis.cs
@@ -0,0 +1,759 @@
+// Copyright (c) 2010-2013 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.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.NRefactory.TypeSystem.Implementation;
+using ICSharpCode.NRefactory.Utils;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ ///
+ /// Represents the definite assignment status of a variable at a specific location.
+ ///
+ public enum DefiniteAssignmentStatus
+ {
+ ///
+ /// The variable might be assigned or unassigned.
+ ///
+ PotentiallyAssigned,
+ ///
+ /// The variable is definitely assigned.
+ ///
+ DefinitelyAssigned,
+ ///
+ /// The variable is definitely assigned iff the expression results in the value 'true'.
+ ///
+ AssignedAfterTrueExpression,
+ ///
+ /// The variable is definitely assigned iff the expression results in the value 'false'.
+ ///
+ AssignedAfterFalseExpression,
+ ///
+ /// The code is unreachable.
+ ///
+ CodeUnreachable
+ }
+
+ ///
+ /// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment)
+ ///
+ public class DefiniteAssignmentAnalysis
+ {
+ sealed class DefiniteAssignmentNode : ControlFlowNode
+ {
+ public int Index;
+ public DefiniteAssignmentStatus NodeStatus;
+
+ public DefiniteAssignmentNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ : base(previousStatement, nextStatement, type)
+ {
+ }
+ }
+
+ sealed class DerivedControlFlowGraphBuilder : ControlFlowGraphBuilder
+ {
+ protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ {
+ return new DefiniteAssignmentNode(previousStatement, nextStatement, type);
+ }
+ }
+
+ readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor();
+ readonly List allNodes = new List();
+ readonly Dictionary beginNodeDict = new Dictionary();
+ readonly Dictionary endNodeDict = new Dictionary();
+ readonly Dictionary conditionNodeDict = new Dictionary();
+ readonly CSharpAstResolver resolver;
+ Dictionary edgeStatus = new Dictionary();
+
+ string variableName;
+ List unassignedVariableUses = new List();
+ int analyzedRangeStart, analyzedRangeEnd;
+ CancellationToken analysisCancellationToken;
+
+ Queue nodesWithModifiedInput = new Queue();
+
+ public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken)
+ : this(rootStatement,
+ new CSharpAstResolver(new CSharpResolver(MinimalCorlib.Instance.CreateCompilation()), rootStatement),
+ cancellationToken)
+ {
+ }
+
+ public DefiniteAssignmentAnalysis(Statement rootStatement, CSharpAstResolver resolver, CancellationToken cancellationToken)
+ {
+ if (rootStatement == null)
+ throw new ArgumentNullException("rootStatement");
+ if (resolver == null)
+ throw new ArgumentNullException("resolver");
+ this.resolver = resolver;
+
+ visitor.analysis = this;
+ DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder();
+ if (resolver.TypeResolveContext.Compilation.MainAssembly.UnresolvedAssembly is MinimalCorlib) {
+ cfgBuilder.EvaluateOnlyPrimitiveConstants = true;
+ }
+ allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolver, cancellationToken).Cast());
+ for (int i = 0; i < allNodes.Count; i++) {
+ DefiniteAssignmentNode node = allNodes[i];
+ node.Index = i; // assign numbers to the nodes
+ if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) {
+ // Anonymous methods have separate control flow graphs, but we also need to analyze those.
+ // Iterate backwards so that anonymous methods are inserted in the correct order
+ for (AstNode child = node.NextStatement.LastChild; child != null; child = child.PrevSibling) {
+ InsertAnonymousMethods(i + 1, child, cfgBuilder, cancellationToken);
+ }
+ }
+ // Now register the node in the dictionaries:
+ if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements)
+ beginNodeDict.Add(node.NextStatement, node);
+ if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode)
+ endNodeDict.Add(node.PreviousStatement, node);
+ if (node.Type == ControlFlowNodeType.LoopCondition)
+ conditionNodeDict.Add(node.NextStatement, node);
+ }
+ // Verify that we created nodes for all statements:
+ Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any());
+ // Verify that we put all nodes into the dictionaries:
+ Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => beginNodeDict.ContainsKey(stmt)));
+ Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => endNodeDict.ContainsKey(stmt)));
+
+ this.analyzedRangeStart = 0;
+ this.analyzedRangeEnd = allNodes.Count - 1;
+ }
+
+ void InsertAnonymousMethods(int insertPos, AstNode node, ControlFlowGraphBuilder cfgBuilder, CancellationToken cancellationToken)
+ {
+ // Ignore any statements, as those have their own ControlFlowNode and get handled separately
+ if (node is Statement)
+ return;
+ AnonymousMethodExpression ame = node as AnonymousMethodExpression;
+ if (ame != null) {
+ allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph(ame.Body, resolver, cancellationToken).Cast());
+ return;
+ }
+ LambdaExpression lambda = node as LambdaExpression;
+ if (lambda != null && lambda.Body is Statement) {
+ allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolver, cancellationToken).Cast());
+ return;
+ }
+ // Descend into child expressions
+ // Iterate backwards so that anonymous methods are inserted in the correct order
+ for (AstNode child = node.LastChild; child != null; child = child.PrevSibling) {
+ InsertAnonymousMethods(insertPos, child, cfgBuilder, cancellationToken);
+ }
+ }
+
+ ///
+ /// Gets the unassigned usages of the previously analyzed variable.
+ ///
+ public IList UnassignedVariableUses {
+ get {
+ return unassignedVariableUses.AsReadOnly();
+ }
+ }
+
+ ///
+ /// Sets the range of statements to be analyzed.
+ /// This method can be used to restrict the analysis to only a part of the method.
+ /// Only the control flow paths that are fully contained within the selected part will be analyzed.
+ ///
+ /// By default, both 'start' and 'end' are inclusive.
+ public void SetAnalyzedRange(Statement start, Statement end, bool startInclusive = true, bool endInclusive = true)
+ {
+ var dictForStart = startInclusive ? beginNodeDict : endNodeDict;
+ var dictForEnd = endInclusive ? endNodeDict : beginNodeDict;
+ Debug.Assert(dictForStart.ContainsKey(start) && dictForEnd.ContainsKey(end));
+ int startIndex = dictForStart[start].Index;
+ int endIndex = dictForEnd[end].Index;
+ if (startIndex > endIndex)
+ throw new ArgumentException("The start statement must be lexically preceding the end statement");
+ this.analyzedRangeStart = startIndex;
+ this.analyzedRangeEnd = endIndex;
+ }
+
+ public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ this.analysisCancellationToken = cancellationToken;
+ this.variableName = variable;
+ try {
+ // Reset the status:
+ unassignedVariableUses.Clear();
+ foreach (DefiniteAssignmentNode node in allNodes) {
+ node.NodeStatus = DefiniteAssignmentStatus.CodeUnreachable;
+ foreach (ControlFlowEdge edge in node.Outgoing)
+ edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable;
+ }
+
+ ChangeNodeStatus(allNodes[analyzedRangeStart], initialStatus);
+ // Iterate as long as the input status of some nodes is changing:
+ while (nodesWithModifiedInput.Count > 0) {
+ DefiniteAssignmentNode node = nodesWithModifiedInput.Dequeue();
+ DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable;
+ foreach (ControlFlowEdge edge in node.Incoming) {
+ inputStatus = MergeStatus(inputStatus, edgeStatus[edge]);
+ }
+ ChangeNodeStatus(node, inputStatus);
+ }
+ } finally {
+ this.analysisCancellationToken = CancellationToken.None;
+ this.variableName = null;
+ }
+ }
+
+ public DefiniteAssignmentStatus GetStatusBefore(Statement statement)
+ {
+ return beginNodeDict[statement].NodeStatus;
+ }
+
+ public DefiniteAssignmentStatus GetStatusAfter(Statement statement)
+ {
+ return endNodeDict[statement].NodeStatus;
+ }
+
+ public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement)
+ {
+ return conditionNodeDict[statement].NodeStatus;
+ }
+
+ ///
+ /// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
+ ///
+ public GraphVizGraph ExportGraph()
+ {
+ GraphVizGraph g = new GraphVizGraph();
+ g.Title = "DefiniteAssignment - " + variableName;
+ for (int i = 0; i < allNodes.Count; i++) {
+ string name = "#" + i + " = " + allNodes[i].NodeStatus.ToString() + Environment.NewLine;
+ switch (allNodes[i].Type) {
+ case ControlFlowNodeType.StartNode:
+ case ControlFlowNodeType.BetweenStatements:
+ name += allNodes[i].NextStatement.ToString();
+ break;
+ case ControlFlowNodeType.EndNode:
+ name += "End of " + allNodes[i].PreviousStatement.ToString();
+ break;
+ case ControlFlowNodeType.LoopCondition:
+ name += "Condition in " + allNodes[i].NextStatement.ToString();
+ break;
+ default:
+ name += allNodes[i].Type.ToString();
+ break;
+ }
+ g.AddNode(new GraphVizNode(i) { label = name });
+ foreach (ControlFlowEdge edge in allNodes[i].Outgoing) {
+ GraphVizEdge ge = new GraphVizEdge(i, ((DefiniteAssignmentNode)edge.To).Index);
+ if (edgeStatus.Count > 0)
+ ge.label = edgeStatus[edge].ToString();
+ if (edge.IsLeavingTryFinally)
+ ge.style = "dashed";
+ switch (edge.Type) {
+ case ControlFlowEdgeType.ConditionTrue:
+ ge.color = "green";
+ break;
+ case ControlFlowEdgeType.ConditionFalse:
+ ge.color = "red";
+ break;
+ case ControlFlowEdgeType.Jump:
+ ge.color = "blue";
+ break;
+ }
+ g.AddEdge(ge);
+ }
+ }
+ return g;
+ }
+
+ static DefiniteAssignmentStatus MergeStatus(DefiniteAssignmentStatus a, DefiniteAssignmentStatus b)
+ {
+ // The result will be DefinitelyAssigned if at least one incoming edge is DefinitelyAssigned and all others are unreachable.
+ // The result will be DefinitelyUnassigned if at least one incoming edge is DefinitelyUnassigned and all others are unreachable.
+ // The result will be Unreachable if all incoming edges are unreachable.
+ // Otherwise, the result will be PotentiallyAssigned.
+
+ if (a == b)
+ return a;
+ else if (a == DefiniteAssignmentStatus.CodeUnreachable)
+ return b;
+ else if (b == DefiniteAssignmentStatus.CodeUnreachable)
+ return a;
+ else
+ return DefiniteAssignmentStatus.PotentiallyAssigned;
+ }
+
+ void ChangeNodeStatus (DefiniteAssignmentNode node, DefiniteAssignmentStatus inputStatus)
+ {
+ if (node.NodeStatus == inputStatus)
+ return;
+ node.NodeStatus = inputStatus;
+ DefiniteAssignmentStatus outputStatus;
+ switch (node.Type) {
+ case ControlFlowNodeType.StartNode:
+ case ControlFlowNodeType.BetweenStatements:
+ if (node.NextStatement is IfElseStatement) {
+ // Handle if-else as a condition node
+ goto case ControlFlowNodeType.LoopCondition;
+ }
+ if (inputStatus == DefiniteAssignmentStatus.DefinitelyAssigned) {
+ // There isn't any way to un-assign variables, so we don't have to check the expression
+ // if the status already is definitely assigned.
+ outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned;
+ } else {
+ outputStatus = CleanSpecialValues (node.NextStatement.AcceptVisitor (visitor, inputStatus));
+ }
+ break;
+ case ControlFlowNodeType.EndNode:
+ outputStatus = inputStatus;
+ if (node.PreviousStatement.Role == TryCatchStatement.FinallyBlockRole
+ && (outputStatus == DefiniteAssignmentStatus.DefinitelyAssigned || outputStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) {
+ TryCatchStatement tryFinally = (TryCatchStatement)node.PreviousStatement.Parent;
+ // Changing the status on a finally block potentially changes the status of all edges leaving that finally block:
+ foreach (ControlFlowEdge edge in allNodes.SelectMany(n => n.Outgoing)) {
+ if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains (tryFinally)) {
+ DefiniteAssignmentStatus s = edgeStatus [edge];
+ if (s == DefiniteAssignmentStatus.PotentiallyAssigned) {
+ ChangeEdgeStatus (edge, outputStatus);
+ }
+ }
+ }
+ }
+ break;
+ case ControlFlowNodeType.LoopCondition:
+ ForeachStatement foreachStmt = node.NextStatement as ForeachStatement;
+ if (foreachStmt != null) {
+ outputStatus = CleanSpecialValues (foreachStmt.InExpression.AcceptVisitor (visitor, inputStatus));
+ if (foreachStmt.VariableName == this.variableName)
+ outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned;
+ break;
+ } else {
+ Debug.Assert (node.NextStatement is IfElseStatement || node.NextStatement is WhileStatement || node.NextStatement is ForStatement || node.NextStatement is DoWhileStatement);
+ Expression condition = node.NextStatement.GetChildByRole (Roles.Condition);
+ if (condition.IsNull)
+ outputStatus = inputStatus;
+ else
+ outputStatus = condition.AcceptVisitor(visitor, inputStatus);
+ foreach (ControlFlowEdge edge in node.Outgoing) {
+ if (edge.Type == ControlFlowEdgeType.ConditionTrue && outputStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) {
+ ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned);
+ } else if (edge.Type == ControlFlowEdgeType.ConditionFalse && outputStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression) {
+ ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned);
+ } else {
+ ChangeEdgeStatus(edge, CleanSpecialValues(outputStatus));
+ }
+ }
+ return;
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+ foreach (ControlFlowEdge edge in node.Outgoing) {
+ ChangeEdgeStatus(edge, outputStatus);
+ }
+ }
+
+ void ChangeEdgeStatus(ControlFlowEdge edge, DefiniteAssignmentStatus newStatus)
+ {
+ DefiniteAssignmentStatus oldStatus = edgeStatus[edge];
+ if (oldStatus == newStatus)
+ return;
+ // Ensure that status can cannot change back to CodeUnreachable after it once was reachable.
+ // Also, don't ever use AssignedAfter... for statements.
+ if (newStatus == DefiniteAssignmentStatus.CodeUnreachable
+ || newStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression
+ || newStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ {
+ throw new InvalidOperationException();
+ }
+ // Note that the status can change from DefinitelyAssigned
+ // back to PotentiallyAssigned as unreachable input edges are
+ // discovered to be reachable.
+
+ edgeStatus[edge] = newStatus;
+ DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To;
+ if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) {
+ // TODO: potential optimization: visit previously unreachable nodes with higher priority
+ // (e.g. use Deque and enqueue previously unreachable nodes at the front, but
+ // other nodes at the end)
+ nodesWithModifiedInput.Enqueue(targetNode);
+ }
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The constant value of the expression; or null if the expression is not a constant.
+ ResolveResult EvaluateConstant(Expression expr)
+ {
+ return resolver.Resolve(expr, analysisCancellationToken);
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The value of the constant boolean expression; or null if the value is not a constant boolean expression.
+ bool? EvaluateCondition(Expression expr)
+ {
+ ResolveResult rr = EvaluateConstant(expr);
+ if (rr != null && rr.IsCompileTimeConstant)
+ return rr.ConstantValue as bool?;
+ else
+ return null;
+ }
+
+ static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status)
+ {
+ if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ return DefiniteAssignmentStatus.PotentiallyAssigned;
+ else if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ return DefiniteAssignmentStatus.PotentiallyAssigned;
+ else
+ return status;
+ }
+
+ sealed class DefiniteAssignmentVisitor : DepthFirstAstVisitor
+ {
+ internal DefiniteAssignmentAnalysis analysis;
+
+ // The general approach for unknown nodes is to pass the status through all child nodes in order
+ protected override DefiniteAssignmentStatus VisitChildren(AstNode node, DefiniteAssignmentStatus data)
+ {
+ // the special values are valid as output only, not as input
+ Debug.Assert(data == CleanSpecialValues(data));
+ DefiniteAssignmentStatus status = data;
+ for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
+ analysis.analysisCancellationToken.ThrowIfCancellationRequested();
+
+ Debug.Assert(!(child is Statement)); // statements are visited with the CFG, not with the visitor pattern
+ status = child.AcceptVisitor(this, status);
+ status = CleanSpecialValues(status);
+ }
+ return status;
+ }
+
+ #region Statements
+ // For statements, the visitor only describes the effect of the statement itself;
+ // we do not consider the effect of any nested statements.
+ // This is done because the nested statements will be reached using the control flow graph.
+
+ // In fact, these methods are present so that the default logic in VisitChildren does not try to visit the nested statements.
+
+ public override DefiniteAssignmentStatus VisitBlockStatement(BlockStatement blockStatement, DefiniteAssignmentStatus data)
+ {
+ return data;
+ }
+
+ public override DefiniteAssignmentStatus VisitCheckedStatement(CheckedStatement checkedStatement, DefiniteAssignmentStatus data)
+ {
+ return data;
+ }
+
+ public override DefiniteAssignmentStatus VisitUncheckedStatement(UncheckedStatement uncheckedStatement, DefiniteAssignmentStatus data)
+ {
+ return data;
+ }
+
+ // ExpressionStatement handled by default logic
+ // VariableDeclarationStatement handled by default logic
+
+ public override DefiniteAssignmentStatus VisitVariableInitializer(VariableInitializer variableInitializer, DefiniteAssignmentStatus data)
+ {
+ if (variableInitializer.Initializer.IsNull) {
+ return data;
+ } else {
+ DefiniteAssignmentStatus status = variableInitializer.Initializer.AcceptVisitor(this, data);
+ if (variableInitializer.Name == analysis.variableName)
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ else
+ return status;
+ }
+ }
+
+ // IfStatement not handled by visitor, but special-cased in the code consuming the control flow graph
+
+ public override DefiniteAssignmentStatus VisitSwitchStatement(SwitchStatement switchStatement, DefiniteAssignmentStatus data)
+ {
+ return switchStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ public override DefiniteAssignmentStatus VisitWhileStatement(WhileStatement whileStatement, DefiniteAssignmentStatus data)
+ {
+ return data; // condition is handled by special condition CFG node
+ }
+
+ public override DefiniteAssignmentStatus VisitDoWhileStatement(DoWhileStatement doWhileStatement, DefiniteAssignmentStatus data)
+ {
+ return data; // condition is handled by special condition CFG node
+ }
+
+ public override DefiniteAssignmentStatus VisitForStatement(ForStatement forStatement, DefiniteAssignmentStatus data)
+ {
+ return data; // condition is handled by special condition CFG node; initializer and iterator statements are handled by CFG
+ }
+
+ // Break/Continue/Goto: handled by default logic
+
+ // ThrowStatement: handled by default logic (just visit the expression)
+ // ReturnStatement: handled by default logic (just visit the expression)
+
+ public override DefiniteAssignmentStatus VisitTryCatchStatement(TryCatchStatement tryCatchStatement, DefiniteAssignmentStatus data)
+ {
+ return data; // no special logic when entering the try-catch-finally statement
+ // TODO: where to put the special logic when exiting the try-finally statement?
+ }
+
+ public override DefiniteAssignmentStatus VisitForeachStatement(ForeachStatement foreachStatement, DefiniteAssignmentStatus data)
+ {
+ return data; // assignment of the foreach loop variable is done when handling the condition node
+ }
+
+ public override DefiniteAssignmentStatus VisitUsingStatement(UsingStatement usingStatement, DefiniteAssignmentStatus data)
+ {
+ if (usingStatement.ResourceAcquisition is Expression)
+ return usingStatement.ResourceAcquisition.AcceptVisitor(this, data);
+ else
+ return data; // don't handle resource acquisition statements, as those are connected in the control flow graph
+ }
+
+ public override DefiniteAssignmentStatus VisitLockStatement(LockStatement lockStatement, DefiniteAssignmentStatus data)
+ {
+ return lockStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ // Yield statements use the default logic
+
+ public override DefiniteAssignmentStatus VisitUnsafeStatement(UnsafeStatement unsafeStatement, DefiniteAssignmentStatus data)
+ {
+ return data;
+ }
+
+ public override DefiniteAssignmentStatus VisitFixedStatement(FixedStatement fixedStatement, DefiniteAssignmentStatus data)
+ {
+ DefiniteAssignmentStatus status = data;
+ foreach (var variable in fixedStatement.Variables)
+ status = variable.AcceptVisitor(this, status);
+ return status;
+ }
+ #endregion
+
+ #region Expressions
+ public override DefiniteAssignmentStatus VisitDirectionExpression(DirectionExpression directionExpression, DefiniteAssignmentStatus data)
+ {
+ if (directionExpression.FieldDirection == FieldDirection.Out) {
+ return HandleAssignment(directionExpression.Expression, null, data);
+ } else {
+ // use default logic for 'ref'
+ return VisitChildren(directionExpression, data);
+ }
+ }
+
+ public override DefiniteAssignmentStatus VisitAssignmentExpression(AssignmentExpression assignmentExpression, DefiniteAssignmentStatus data)
+ {
+ if (assignmentExpression.Operator == AssignmentOperatorType.Assign) {
+ return HandleAssignment(assignmentExpression.Left, assignmentExpression.Right, data);
+ } else {
+ // use default logic for compound assignment operators
+ return VisitChildren(assignmentExpression, data);
+ }
+ }
+
+ DefiniteAssignmentStatus HandleAssignment(Expression left, Expression right, DefiniteAssignmentStatus initialStatus)
+ {
+ IdentifierExpression ident = left as IdentifierExpression;
+ if (ident != null && ident.Identifier == analysis.variableName) {
+ // right==null is special case when handling 'out' expressions
+ if (right != null)
+ right.AcceptVisitor(this, initialStatus);
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ } else {
+ DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus);
+ if (right != null)
+ status = right.AcceptVisitor(this, CleanSpecialValues(status));
+ return CleanSpecialValues(status);
+ }
+ }
+
+ public override DefiniteAssignmentStatus VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, DefiniteAssignmentStatus data)
+ {
+ // Don't use the default logic here because we don't want to clean up the special values.
+ return parenthesizedExpression.Expression.AcceptVisitor(this, data);
+ }
+
+ public override DefiniteAssignmentStatus VisitCheckedExpression(CheckedExpression checkedExpression, DefiniteAssignmentStatus data)
+ {
+ return checkedExpression.Expression.AcceptVisitor(this, data);
+ }
+
+ public override DefiniteAssignmentStatus VisitUncheckedExpression(UncheckedExpression uncheckedExpression, DefiniteAssignmentStatus data)
+ {
+ return uncheckedExpression.Expression.AcceptVisitor(this, data);
+ }
+
+ public override DefiniteAssignmentStatus VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, DefiniteAssignmentStatus data)
+ {
+ if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalAnd) {
+ // Handle constant left side of && expressions (not in the C# spec, but done by the MS compiler)
+ bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left);
+ if (cond == true)
+ return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
+ else if (cond == false)
+ return data; // right operand never gets evaluated
+ // C# 4.0 spec: §5.3.3.24 Definite Assignment for && expressions
+ DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ DefiniteAssignmentStatus beforeRight;
+ if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned;
+ else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned;
+ else
+ beforeRight = afterLeft;
+ DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight);
+ if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned)
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
+ else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
+ else
+ return DefiniteAssignmentStatus.PotentiallyAssigned;
+ } else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) {
+ // C# 4.0 spec: §5.3.3.25 Definite Assignment for || expressions
+ bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left);
+ if (cond == false)
+ return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
+ else if (cond == true)
+ return data; // right operand never gets evaluated
+ DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ DefiniteAssignmentStatus beforeRight;
+ if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned;
+ else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned;
+ else
+ beforeRight = afterLeft;
+ DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight);
+ if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned)
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ return DefiniteAssignmentStatus.DefinitelyAssigned;
+ else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
+ else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
+ else
+ return DefiniteAssignmentStatus.PotentiallyAssigned;
+ } else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
+ // C# 4.0 spec: §5.3.3.27 Definite assignment for ?? expressions
+ ResolveResult crr = analysis.EvaluateConstant(binaryOperatorExpression.Left);
+ if (crr != null && crr.IsCompileTimeConstant && crr.ConstantValue == null)
+ return binaryOperatorExpression.Right.AcceptVisitor(this, data);
+ DefiniteAssignmentStatus status = CleanSpecialValues(binaryOperatorExpression.Left.AcceptVisitor(this, data));
+ binaryOperatorExpression.Right.AcceptVisitor(this, status);
+ return status;
+ } else {
+ // use default logic for other operators
+ return VisitChildren(binaryOperatorExpression, data);
+ }
+ }
+
+ public override DefiniteAssignmentStatus VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, DefiniteAssignmentStatus data)
+ {
+ if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) {
+ // C# 4.0 spec: §5.3.3.26 Definite assignment for ! expressions
+ DefiniteAssignmentStatus status = unaryOperatorExpression.Expression.AcceptVisitor(this, data);
+ if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
+ return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
+ else if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
+ return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
+ else
+ return status;
+ } else {
+ // use default logic for other operators
+ return VisitChildren(unaryOperatorExpression, data);
+ }
+ }
+
+ public override DefiniteAssignmentStatus VisitConditionalExpression(ConditionalExpression conditionalExpression, DefiniteAssignmentStatus data)
+ {
+ // C# 4.0 spec: §5.3.3.28 Definite assignment for ?: expressions
+ bool? cond = analysis.EvaluateCondition(conditionalExpression.Condition);
+ if (cond == true) {
+ return conditionalExpression.TrueExpression.AcceptVisitor(this, data);
+ } else if (cond == false) {
+ return conditionalExpression.FalseExpression.AcceptVisitor(this, data);
+ } else {
+ DefiniteAssignmentStatus afterCondition = conditionalExpression.Condition.AcceptVisitor(this, data);
+
+ DefiniteAssignmentStatus beforeTrue, beforeFalse;
+ if (afterCondition == DefiniteAssignmentStatus.AssignedAfterTrueExpression) {
+ beforeTrue = DefiniteAssignmentStatus.DefinitelyAssigned;
+ beforeFalse = DefiniteAssignmentStatus.PotentiallyAssigned;
+ } else if (afterCondition == DefiniteAssignmentStatus.AssignedAfterFalseExpression) {
+ beforeTrue = DefiniteAssignmentStatus.PotentiallyAssigned;
+ beforeFalse = DefiniteAssignmentStatus.DefinitelyAssigned;
+ } else {
+ beforeTrue = afterCondition;
+ beforeFalse = afterCondition;
+ }
+
+ DefiniteAssignmentStatus afterTrue = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeTrue);
+ DefiniteAssignmentStatus afterFalse = conditionalExpression.FalseExpression.AcceptVisitor(this, beforeFalse);
+ return MergeStatus(CleanSpecialValues(afterTrue), CleanSpecialValues(afterFalse));
+ }
+ }
+
+ public override DefiniteAssignmentStatus VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, DefiniteAssignmentStatus data)
+ {
+ BlockStatement body = anonymousMethodExpression.Body;
+ analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data);
+ return data;
+ }
+
+ public override DefiniteAssignmentStatus VisitLambdaExpression(LambdaExpression lambdaExpression, DefiniteAssignmentStatus data)
+ {
+ Statement body = lambdaExpression.Body as Statement;
+ if (body != null) {
+ analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data);
+ } else {
+ lambdaExpression.Body.AcceptVisitor(this, data);
+ }
+ return data;
+ }
+
+ public override DefiniteAssignmentStatus VisitIdentifierExpression(IdentifierExpression identifierExpression, DefiniteAssignmentStatus data)
+ {
+ if (data != DefiniteAssignmentStatus.DefinitelyAssigned
+ && identifierExpression.Identifier == analysis.variableName && identifierExpression.TypeArguments.Count == 0)
+ {
+ analysis.unassignedVariableUses.Add(identifierExpression);
+ }
+ return data;
+ }
+ #endregion
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueAnalysis.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueAnalysis.cs
new file mode 100644
index 000000000..a20b4a0f0
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueAnalysis.cs
@@ -0,0 +1,2215 @@
+//
+// NullValueAnalysis.cs
+//
+// Author:
+// Luís Reis
+//
+// Copyright (c) 2013 Luís Reis
+//
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Text;
+using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.NRefactory.CSharp.Refactoring;
+using ICSharpCode.NRefactory.PatternMatching;
+using ICSharpCode.NRefactory.CSharp;
+using ICSharpCode.NRefactory.Utils;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ public class NullValueAnalysis
+ {
+ sealed class VariableStatusInfo : IEquatable, IEnumerable>
+ {
+ readonly Dictionary VariableStatus = new Dictionary();
+
+ public NullValueStatus this[string name]
+ {
+ get {
+ NullValueStatus status;
+ if (VariableStatus.TryGetValue(name, out status)) {
+ return status;
+ }
+ return NullValueStatus.UnreachableOrInexistent;
+ }
+ set {
+ if (value == NullValueStatus.UnreachableOrInexistent) {
+ VariableStatus.Remove(name);
+ } else {
+ VariableStatus [name] = value;
+ }
+ }
+ }
+
+ ///
+ /// Modifies the variable state to consider a new incoming path
+ ///
+ /// true, if the state has changed, false otherwise.
+ /// The variable state of the incoming path
+ public bool ReceiveIncoming(VariableStatusInfo incomingState)
+ {
+ bool changed = false;
+ var listOfVariables = VariableStatus.Keys.Concat(incomingState.VariableStatus.Keys).ToList();
+ foreach (string variable in listOfVariables)
+ {
+ var newValue = CombineStatus(this [variable], incomingState [variable]);
+ if (this [variable] != newValue) {
+ this [variable] = newValue;
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+
+ public static NullValueStatus CombineStatus(NullValueStatus oldValue, NullValueStatus incomingValue)
+ {
+ if (oldValue == NullValueStatus.Error || incomingValue == NullValueStatus.Error)
+ return NullValueStatus.Error;
+
+ if (oldValue == NullValueStatus.UnreachableOrInexistent ||
+ oldValue == NullValueStatus.Unassigned)
+ return incomingValue;
+
+ if (incomingValue == NullValueStatus.Unassigned) {
+ return NullValueStatus.Unassigned;
+ }
+
+ if (oldValue == NullValueStatus.CapturedUnknown || incomingValue == NullValueStatus.CapturedUnknown) {
+ //TODO: Check if this is right
+ return NullValueStatus.CapturedUnknown;
+ }
+
+ if (oldValue == NullValueStatus.Unknown) {
+ return NullValueStatus.Unknown;
+ }
+
+ if (oldValue == NullValueStatus.DefinitelyNull) {
+ return incomingValue == NullValueStatus.DefinitelyNull ?
+ NullValueStatus.DefinitelyNull : NullValueStatus.PotentiallyNull;
+ }
+
+ if (oldValue == NullValueStatus.DefinitelyNotNull) {
+ if (incomingValue == NullValueStatus.Unknown)
+ return NullValueStatus.Unknown;
+ if (incomingValue == NullValueStatus.DefinitelyNotNull)
+ return NullValueStatus.DefinitelyNotNull;
+ return NullValueStatus.PotentiallyNull;
+ }
+
+ Debug.Assert(oldValue == NullValueStatus.PotentiallyNull);
+ return NullValueStatus.PotentiallyNull;
+ }
+
+ public bool HasVariable(string variable) {
+ return VariableStatus.ContainsKey(variable);
+ }
+
+ public VariableStatusInfo Clone() {
+ var clone = new VariableStatusInfo();
+ foreach (var item in VariableStatus) {
+ clone.VariableStatus.Add(item.Key, item.Value);
+ }
+ return clone;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as VariableStatusInfo);
+ }
+
+ public bool Equals(VariableStatusInfo obj)
+ {
+ if (obj == null) {
+ return false;
+ }
+
+ if (VariableStatus.Count != obj.VariableStatus.Count)
+ return false;
+
+ return VariableStatus.All(item => item.Value == obj[item.Key]);
+ }
+
+ public override int GetHashCode()
+ {
+ //STUB
+ return VariableStatus.Count.GetHashCode();
+ }
+
+ public static bool operator ==(VariableStatusInfo obj1, VariableStatusInfo obj2) {
+ return object.ReferenceEquals(obj1, null) ?
+ object.ReferenceEquals(obj2, null) : obj1.Equals(obj2);
+ }
+
+ public static bool operator !=(VariableStatusInfo obj1, VariableStatusInfo obj2) {
+ return !(obj1 == obj2);
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return VariableStatus.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public override string ToString()
+ {
+ var builder = new StringBuilder("[");
+ foreach (var item in this) {
+ builder.Append(item.Key);
+ builder.Append("=");
+ builder.Append(item.Value);
+ }
+ builder.Append("]");
+ return builder.ToString();
+ }
+ }
+
+ sealed class NullAnalysisNode : ControlFlowNode
+ {
+ public readonly VariableStatusInfo VariableState = new VariableStatusInfo();
+ public bool Visited { get; private set; }
+
+ public NullAnalysisNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ : base(previousStatement, nextStatement, type)
+ {
+ }
+
+ public bool ReceiveIncoming(VariableStatusInfo incomingState)
+ {
+ bool changed = VariableState.ReceiveIncoming(incomingState);
+ if (!Visited) {
+ Visited = true;
+ return true;
+ }
+ return changed;
+ }
+ }
+
+ sealed class NullAnalysisGraphBuilder : ControlFlowGraphBuilder
+ {
+ protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
+ {
+ return new NullAnalysisNode(previousStatement, nextStatement, type);
+ }
+ }
+
+ class PendingNode : IEquatable {
+ internal readonly NullAnalysisNode nodeToVisit;
+ internal readonly VariableStatusInfo statusInfo;
+ internal readonly ComparableList pendingTryFinallyNodes;
+ internal readonly NullAnalysisNode nodeAfterFinally;
+
+ internal PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo)
+ : this(nodeToVisit, statusInfo, new ComparableList(), null)
+ {
+ }
+
+ public PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo, ComparableList pendingFinallyNodes, NullAnalysisNode nodeAfterFinally)
+ {
+ this.nodeToVisit = nodeToVisit;
+ this.statusInfo = statusInfo;
+ this.pendingTryFinallyNodes = pendingFinallyNodes;
+ this.nodeAfterFinally = nodeAfterFinally;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as PendingNode);
+ }
+
+ public bool Equals(PendingNode obj) {
+ if (obj == null) return false;
+
+ if (nodeToVisit != obj.nodeToVisit) return false;
+ if (statusInfo != obj.statusInfo) return false;
+ if (pendingTryFinallyNodes != obj.pendingTryFinallyNodes) return false;
+ if (nodeAfterFinally != obj.nodeAfterFinally) return false;
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ return nodeToVisit.GetHashCode() ^
+ statusInfo.GetHashCode() ^
+ pendingTryFinallyNodes.GetHashCode() ^
+ (nodeAfterFinally == null ? 0 : nodeAfterFinally.GetHashCode());
+ }
+ }
+
+ readonly BaseRefactoringContext context;
+ readonly NullAnalysisVisitor visitor;
+ List allNodes;
+ readonly HashSet nodesToVisit = new HashSet();
+ Dictionary nodeBeforeStatementDict;
+ Dictionary nodeAfterStatementDict;
+ readonly Dictionary expressionResult = new Dictionary();
+
+ public NullValueAnalysis(BaseRefactoringContext context, MethodDeclaration methodDeclaration, CancellationToken cancellationToken)
+ : this(context, methodDeclaration.Body, methodDeclaration.Parameters, cancellationToken)
+ {
+ }
+
+ readonly IEnumerable parameters;
+ readonly Statement rootStatement;
+
+ readonly CancellationToken cancellationToken;
+
+ public NullValueAnalysis(BaseRefactoringContext context, Statement rootStatement, IEnumerable parameters, CancellationToken cancellationToken)
+ {
+ if (rootStatement == null)
+ throw new ArgumentNullException("rootStatement");
+ if (context == null)
+ throw new ArgumentNullException("context");
+
+ this.context = context;
+ this.rootStatement = rootStatement;
+ this.parameters = parameters;
+ this.visitor = new NullAnalysisVisitor(this);
+ this.cancellationToken = cancellationToken;
+ }
+
+ ///
+ /// Sets the local variable value.
+ /// This method does not change anything if identifier does not refer to a local variable.
+ /// Do not use this in variable declarations since resolving the variable won't work yet.
+ ///
+ /// true, if local variable value was set, false otherwise.
+ /// The variable status data to change.
+ /// The identifier to set.
+ /// The name of the identifier to set.
+ /// The value to set the identifier.
+ bool SetLocalVariableValue (VariableStatusInfo data, AstNode identifierNode, string identifierName, NullValueStatus value) {
+ var resolveResult = context.Resolve(identifierNode);
+ if (resolveResult is LocalResolveResult) {
+ if (data [identifierName] != NullValueStatus.CapturedUnknown) {
+ data [identifierName] = value;
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool SetLocalVariableValue (VariableStatusInfo data, IdentifierExpression identifierExpression, NullValueStatus value) {
+ return SetLocalVariableValue(data, identifierExpression, identifierExpression.Identifier, value);
+ }
+
+ bool SetLocalVariableValue (VariableStatusInfo data, Identifier identifier, NullValueStatus value) {
+ return SetLocalVariableValue(data, identifier, identifier.Name, value);
+ }
+
+ void SetupNode(NullAnalysisNode node)
+ {
+ foreach (var parameter in parameters) {
+ var resolveResult = context.Resolve(parameter.Type);
+ node.VariableState[parameter.Name] = GetInitialVariableStatus(resolveResult);
+ }
+
+ nodesToVisit.Add(new PendingNode(node, node.VariableState));
+ }
+
+ static bool IsTypeNullable(IType type)
+ {
+ return type.IsReferenceType == true || type.FullName == "System.Nullable";
+ }
+
+ public bool IsParametersAreUninitialized {
+ get;
+ set;
+ }
+
+ NullValueStatus GetInitialVariableStatus(ResolveResult resolveResult)
+ {
+ var typeResolveResult = resolveResult as TypeResolveResult;
+ if (typeResolveResult == null) {
+ return NullValueStatus.Error;
+ }
+ var type = typeResolveResult.Type;
+ if (type.IsReferenceType == null) {
+ return NullValueStatus.Error;
+ }
+ if (!IsParametersAreUninitialized)
+ return NullValueStatus.DefinitelyNotNull;
+ return IsTypeNullable(type) ? NullValueStatus.PotentiallyNull : NullValueStatus.DefinitelyNotNull;
+ }
+
+ public void Analyze()
+ {
+ var cfgBuilder = new NullAnalysisGraphBuilder();
+ allNodes = cfgBuilder.BuildControlFlowGraph(rootStatement, cancellationToken).Cast().ToList();
+ nodeBeforeStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements)
+ .ToDictionary(node => node.NextStatement);
+ nodeAfterStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode)
+ .ToDictionary(node => node.PreviousStatement);
+
+ foreach (var node in allNodes) {
+ if (node.Type == ControlFlowNodeType.StartNode && node.NextStatement == rootStatement) {
+ Debug.Assert(!nodesToVisit.Any());
+
+ SetupNode(node);
+ }
+ }
+
+ while (nodesToVisit.Any()) {
+ var nodeToVisit = nodesToVisit.First();
+ nodesToVisit.Remove(nodeToVisit);
+
+ Visit(nodeToVisit);
+ }
+ }
+
+ int visits = 0;
+
+ public int NodeVisits
+ {
+ get {
+ return visits;
+ }
+ }
+
+ void Visit(PendingNode nodeInfo)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var node = nodeInfo.nodeToVisit;
+ var statusInfo = nodeInfo.statusInfo;
+
+ visits++;
+ if (visits > 100) {
+ //Visiting way too often, let's enter fast mode
+ //Fast mode is slighly less accurate but visits each node less times
+ nodesToVisit.RemoveWhere(candidate => candidate.nodeToVisit == nodeInfo.nodeToVisit &&
+ candidate.pendingTryFinallyNodes.Equals(nodeInfo.pendingTryFinallyNodes) &&
+ candidate.nodeAfterFinally == nodeInfo.nodeAfterFinally);
+ statusInfo = node.VariableState;
+ }
+
+ var nextStatement = node.NextStatement;
+ VariableStatusInfo outgoingStatusInfo = statusInfo;
+ VisitorResult result = null;
+
+ if (nextStatement != null && (!(nextStatement is DoWhileStatement) || node.Type == ControlFlowNodeType.LoopCondition)) {
+ result = nextStatement.AcceptVisitor(visitor, statusInfo);
+ if (result == null) {
+ Console.WriteLine("Failure in {0}", nextStatement);
+ throw new InvalidOperationException();
+ }
+
+ outgoingStatusInfo = result.Variables;
+ }
+
+ if ((result == null || !result.ThrowsException) && node.Outgoing.Any()) {
+ var tryFinallyStatement = nextStatement as TryCatchStatement;
+
+ foreach (var outgoingEdge in node.Outgoing) {
+ VariableStatusInfo edgeInfo;
+ edgeInfo = outgoingStatusInfo.Clone();
+
+ if (node.Type == ControlFlowNodeType.EndNode) {
+ var previousBlock = node.PreviousStatement as BlockStatement;
+ if (previousBlock != null) {
+ //We're leaving a block statement.
+ //As such, we'll remove the variables that were declared *in* the loop
+ //This helps GetVariableStatusAfter/BeforeStatement be more accurate
+ //and prevents some redundant revisiting.
+
+ foreach (var variableInitializer in previousBlock.Statements
+ .OfType()
+ .SelectMany(declaration => declaration.Variables)) {
+
+ edgeInfo [variableInitializer.Name] = NullValueStatus.UnreachableOrInexistent;
+ }
+ }
+ }
+
+ if (tryFinallyStatement != null) {
+ //With the exception of try statements, this needs special handling:
+ //we'll set all changed variables to Unknown or CapturedUnknown
+ if (outgoingEdge.To.NextStatement == tryFinallyStatement.FinallyBlock) {
+ foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType()) {
+ //TODO: Investigate CaptureUnknown
+ SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown);
+ }
+ } else {
+ var clause = tryFinallyStatement.CatchClauses
+ .FirstOrDefault(candidateClause => candidateClause.Body == outgoingEdge.To.NextStatement);
+
+ if (clause != null) {
+ SetLocalVariableValue(edgeInfo, clause.VariableNameToken, NullValueStatus.DefinitelyNotNull);
+
+ foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType()) {
+ //TODO: Investigate CaptureUnknown
+ SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown);
+ }
+ }
+ }
+ }
+
+ if (result != null) {
+ switch (outgoingEdge.Type) {
+ case ControlFlowEdgeType.ConditionTrue:
+ if (result.KnownBoolResult == false) {
+ //No need to explore this path -- expression is known to be false
+ continue;
+ }
+ edgeInfo = result.TruePathVariables;
+ break;
+ case ControlFlowEdgeType.ConditionFalse:
+ if (result.KnownBoolResult == true) {
+ //No need to explore this path -- expression is known to be true
+ continue;
+ }
+ edgeInfo = result.FalsePathVariables;
+ break;
+ }
+ }
+
+ if (outgoingEdge.IsLeavingTryFinally) {
+ var nodeAfterFinally = (NullAnalysisNode)outgoingEdge.To;
+ var finallyNodes = outgoingEdge.TryFinallyStatements.Select(tryFinally => nodeBeforeStatementDict [tryFinally.FinallyBlock]).ToList();
+ var nextNode = finallyNodes.First();
+ var remainingFinallyNodes = new ComparableList(finallyNodes.Skip(1));
+ //We have to visit the node even if ReceiveIncoming returns false
+ //since the finallyNodes/nodeAfterFinally might be different even if the values of variables are the same -- and they need to be visited either way!
+ //TODO 1: Is there any point in visiting the finally statement here?
+ //TODO 2: Do we need the ReceiveIncoming at all?
+ nextNode.ReceiveIncoming(edgeInfo);
+ nodesToVisit.Add(new PendingNode(nextNode, edgeInfo, remainingFinallyNodes, nodeAfterFinally));
+ } else {
+ var outgoingNode = (NullAnalysisNode)outgoingEdge.To;
+ if (outgoingNode.ReceiveIncoming(edgeInfo)) {
+ nodesToVisit.Add(new PendingNode(outgoingNode, edgeInfo));
+ }
+ }
+ }
+ } else {
+ //We found a return/throw/yield break or some other termination node
+ var finallyBlockStarts = nodeInfo.pendingTryFinallyNodes;
+ var nodeAfterFinally = nodeInfo.nodeAfterFinally;
+
+ if (finallyBlockStarts.Any()) {
+ var nextNode = finallyBlockStarts.First();
+ if (nextNode.ReceiveIncoming(outgoingStatusInfo))
+ nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo, new ComparableList(finallyBlockStarts.Skip(1)), nodeInfo.nodeAfterFinally));
+ } else if (nodeAfterFinally != null && nodeAfterFinally.ReceiveIncoming(outgoingStatusInfo)) {
+ nodesToVisit.Add(new PendingNode(nodeAfterFinally, outgoingStatusInfo));
+ } else {
+ //Maybe we finished a try/catch/finally statement the "normal" way (no direct jumps)
+ //so let's check that case
+ var statement = node.PreviousStatement ?? node.NextStatement;
+ Debug.Assert(statement != null);
+ var parent = statement.GetParent();
+ var parentTryCatch = parent as TryCatchStatement;
+ if (parentTryCatch != null) {
+ var nextNode = nodeAfterStatementDict [parentTryCatch];
+ if (nextNode.ReceiveIncoming(outgoingStatusInfo)) {
+ nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo));
+ }
+ }
+ }
+ }
+ }
+
+ public NullValueStatus GetExpressionResult(Expression expr)
+ {
+ if (expr == null)
+ throw new ArgumentNullException("expr");
+
+ NullValueStatus info;
+ if (expressionResult.TryGetValue(expr, out info)) {
+ return info;
+ }
+
+ return NullValueStatus.UnreachableOrInexistent;
+ }
+
+ public NullValueStatus GetVariableStatusBeforeStatement(Statement stmt, string variableName)
+ {
+ if (stmt == null)
+ throw new ArgumentNullException("stmt");
+ if (variableName == null)
+ throw new ArgumentNullException("variableName");
+
+ NullAnalysisNode node;
+ if (nodeBeforeStatementDict.TryGetValue(stmt, out node)) {
+ return node.VariableState [variableName];
+ }
+
+ return NullValueStatus.UnreachableOrInexistent;
+ }
+
+ public NullValueStatus GetVariableStatusAfterStatement(Statement stmt, string variableName)
+ {
+ if (stmt == null)
+ throw new ArgumentNullException("stmt");
+ if (variableName == null)
+ throw new ArgumentNullException("variableName");
+
+ NullAnalysisNode node;
+ if (nodeAfterStatementDict.TryGetValue(stmt, out node)) {
+ return node.VariableState [variableName];
+ }
+
+ return NullValueStatus.UnreachableOrInexistent;
+ }
+
+ class ConditionalBranchInfo
+ {
+ ///
+ /// True if the variable is null for the true path, false if it is false for the true path.
+ ///
+ public Dictionary TrueResultVariableNullStates = new Dictionary();
+ ///
+ /// True if the variable is null for the false path, false if it is false for the false path.
+ ///
+ public Dictionary FalseResultVariableNullStates = new Dictionary();
+ }
+
+ class VisitorResult
+ {
+ ///
+ /// Indicates the return value of the expression.
+ ///
+ ///
+ /// Only applicable for expressions.
+ ///
+ public NullValueStatus NullableReturnResult;
+
+ ///
+ /// Indicates the value of each item in an array or linq query.
+ ///
+ public NullValueStatus EnumeratedValueResult;
+
+ ///
+ /// Information that indicates the restrictions to add
+ /// when branching.
+ ///
+ ///
+ /// Used in if/else statements, conditional expressions and
+ /// while statements.
+ ///
+ public ConditionalBranchInfo ConditionalBranchInfo;
+
+ ///
+ /// The state of the variables after the expression is executed.
+ ///
+ public VariableStatusInfo Variables;
+
+ ///
+ /// The expression is known to be invalid and trigger an error
+ /// (e.g. a NullReferenceException)
+ ///
+ public bool ThrowsException;
+
+ ///
+ /// The known bool result of an expression.
+ ///
+ public bool? KnownBoolResult;
+
+ public static VisitorResult ForEnumeratedValue(VariableStatusInfo variables, NullValueStatus itemValues)
+ {
+ var result = new VisitorResult();
+ result.NullableReturnResult = NullValueStatus.DefinitelyNotNull;
+ result.EnumeratedValueResult = itemValues;
+ result.Variables = variables.Clone();
+ return result;
+ }
+
+ public static VisitorResult ForValue(VariableStatusInfo variables, NullValueStatus returnValue)
+ {
+ var result = new VisitorResult();
+ result.NullableReturnResult = returnValue;
+ result.Variables = variables.Clone();
+ return result;
+ }
+
+ public static VisitorResult ForBoolValue(VariableStatusInfo variables, bool newValue)
+ {
+ var result = new VisitorResult();
+ result.NullableReturnResult = NullValueStatus.DefinitelyNotNull; //Bool expressions are never null
+ result.KnownBoolResult = newValue;
+ result.Variables = variables.Clone();
+ return result;
+ }
+
+ public static VisitorResult ForException(VariableStatusInfo variables) {
+ var result = new VisitorResult();
+ result.NullableReturnResult = NullValueStatus.UnreachableOrInexistent;
+ result.ThrowsException = true;
+ result.Variables = variables.Clone();
+ return result;
+ }
+
+ public VisitorResult Negated {
+ get {
+ var result = new VisitorResult();
+ if (NullableReturnResult.IsDefiniteValue()) {
+ result.NullableReturnResult = NullableReturnResult == NullValueStatus.DefinitelyNull
+ ? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull;
+ } else {
+ result.NullableReturnResult = NullableReturnResult;
+ }
+ result.Variables = Variables.Clone();
+ result.KnownBoolResult = !KnownBoolResult;
+ if (ConditionalBranchInfo != null) {
+ result.ConditionalBranchInfo = new ConditionalBranchInfo();
+ foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) {
+ result.ConditionalBranchInfo.FalseResultVariableNullStates [item.Key] = item.Value;
+ }
+ foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) {
+ result.ConditionalBranchInfo.TrueResultVariableNullStates [item.Key] = item.Value;
+ }
+ }
+ return result;
+ }
+ }
+
+ public VariableStatusInfo TruePathVariables {
+ get {
+ var variables = Variables.Clone();
+ if (ConditionalBranchInfo != null) {
+ foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) {
+ variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
+ }
+ }
+ return variables;
+ }
+ }
+
+ public VariableStatusInfo FalsePathVariables {
+ get {
+ var variables = Variables.Clone();
+ if (ConditionalBranchInfo != null) {
+ foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) {
+ variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
+ }
+ }
+ return variables;
+ }
+ }
+
+ public static VisitorResult AndOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult)
+ {
+ var result = new VisitorResult();
+ result.KnownBoolResult = tentativeLeftResult.KnownBoolResult & tentativeRightResult.KnownBoolResult;
+
+ var trueTruePath = tentativeRightResult.TruePathVariables;
+ var trueFalsePath = tentativeRightResult.FalsePathVariables;
+ var falsePath = tentativeLeftResult.FalsePathVariables;
+
+ var trueVariables = trueTruePath;
+
+ var falseVariables = trueFalsePath.Clone();
+ falseVariables.ReceiveIncoming(falsePath);
+ result.Variables = trueVariables.Clone();
+ result.Variables.ReceiveIncoming(falseVariables);
+
+ result.ConditionalBranchInfo = new ConditionalBranchInfo();
+
+ foreach (var variable in trueVariables) {
+ if (!variable.Value.IsDefiniteValue())
+ continue;
+
+ string variableName = variable.Key;
+
+ if (variable.Value != result.Variables[variableName]) {
+ bool isNull = variable.Value == NullValueStatus.DefinitelyNull;
+ result.ConditionalBranchInfo.TrueResultVariableNullStates.Add(variableName, isNull);
+ }
+ }
+
+ foreach (var variable in falseVariables) {
+ if (!variable.Value.IsDefiniteValue())
+ continue;
+
+ string variableName = variable.Key;
+
+ if (variable.Value != result.Variables [variableName]) {
+ bool isNull = variable.Value == NullValueStatus.DefinitelyNull;
+ result.ConditionalBranchInfo.FalseResultVariableNullStates.Add(variableName, isNull);
+ }
+ }
+
+ return result;
+ }
+
+ public static VisitorResult OrOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult)
+ {
+ return VisitorResult.AndOperation(tentativeLeftResult.Negated, tentativeRightResult.Negated).Negated;
+ }
+ }
+
+ class NullAnalysisVisitor : DepthFirstAstVisitor
+ {
+ NullValueAnalysis analysis;
+
+ public NullAnalysisVisitor(NullValueAnalysis analysis) {
+ this.analysis = analysis;
+ }
+
+ protected override VisitorResult VisitChildren(AstNode node, VariableStatusInfo data)
+ {
+ Debug.Fail("Missing override for " + node.GetType().Name);
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitNullNode(AstNode nullNode, VariableStatusInfo data)
+ {
+ // can occur due to syntax errors
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitEmptyStatement(EmptyStatement emptyStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitBlockStatement(BlockStatement blockStatement, VariableStatusInfo data)
+ {
+ //We'll visit the child statements later (we'll visit each one directly from the CFG)
+ //As such this is mostly a dummy node.
+ return new VisitorResult { Variables = data };
+ }
+
+ public override VisitorResult VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, VariableStatusInfo data)
+ {
+ foreach (var variable in variableDeclarationStatement.Variables) {
+ var result = variable.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables;
+ }
+
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitVariableInitializer(VariableInitializer variableInitializer, VariableStatusInfo data)
+ {
+ if (variableInitializer.Initializer.IsNull) {
+ data = data.Clone();
+ data[variableInitializer.Name] = NullValueStatus.Unassigned;
+ } else {
+ var result = variableInitializer.Initializer.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables.Clone();
+ data[variableInitializer.Name] = result.NullableReturnResult;
+ }
+
+ return VisitorResult.ForValue(data, data [variableInitializer.Name]);
+ }
+
+ public override VisitorResult VisitIfElseStatement(IfElseStatement ifElseStatement, VariableStatusInfo data)
+ {
+ //We'll visit the true/false statements later (directly from the CFG)
+ return ifElseStatement.Condition.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitWhileStatement(WhileStatement whileStatement, VariableStatusInfo data)
+ {
+ return whileStatement.Condition.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitDoWhileStatement(DoWhileStatement doWhileStatement, VariableStatusInfo data)
+ {
+ return doWhileStatement.Condition.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitForStatement(ForStatement forStatement, VariableStatusInfo data)
+ {
+ //The initializers, the embedded statement and the iterators aren't visited here
+ //because they have their own CFG nodes.
+ if (forStatement.Condition.IsNull)
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ return forStatement.Condition.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitForeachStatement(ForeachStatement foreachStatement, VariableStatusInfo data)
+ {
+ var newVariable = foreachStatement.VariableNameToken;
+ var inExpressionResult = foreachStatement.InExpression.AcceptVisitor(this, data);
+ if (inExpressionResult.ThrowsException)
+ return inExpressionResult;
+
+ var newData = inExpressionResult.Variables.Clone();
+
+ var resolveResult = analysis.context.Resolve(foreachStatement.VariableNameToken) as LocalResolveResult;
+ if (resolveResult != null) {
+ //C# 5.0 changed the meaning of foreach so that each iteration declares a new variable
+ //as such, the variable is "uncaptured" only for C# >= 5.0
+ if (analysis.context.Supports(new Version(5, 0)) || data[newVariable.Name] != NullValueStatus.CapturedUnknown) {
+ newData[newVariable.Name] = NullValueAnalysis.IsTypeNullable(resolveResult.Type) ? inExpressionResult.EnumeratedValueResult : NullValueStatus.DefinitelyNotNull;
+ }
+ }
+
+ return VisitorResult.ForValue(newData, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitUsingStatement(UsingStatement usingStatement, VariableStatusInfo data)
+ {
+ return usingStatement.ResourceAcquisition.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitFixedStatement(FixedStatement fixedStatement, VariableStatusInfo data)
+ {
+ foreach (var variable in fixedStatement.Variables) {
+ var result = variable.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables;
+ }
+
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitSwitchStatement(SwitchStatement switchStatement, VariableStatusInfo data)
+ {
+ //We could do better than this, but it would require special handling outside the visitor
+ //so for now, for simplicity, we'll just take the easy way
+
+ var tentativeResult = switchStatement.Expression.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException) {
+ return tentativeResult;
+ }
+
+ foreach (var section in switchStatement.SwitchSections) {
+ //No need to check for ThrowsException, since it will always be false (see VisitSwitchSection)
+ section.AcceptVisitor(this, tentativeResult.Variables);
+ }
+
+ return VisitorResult.ForValue(tentativeResult.Variables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitSwitchSection(SwitchSection switchSection, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitExpressionStatement(ExpressionStatement expressionStatement, VariableStatusInfo data)
+ {
+ return expressionStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitReturnStatement(ReturnStatement returnStatement, VariableStatusInfo data)
+ {
+ if (returnStatement.Expression.IsNull)
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ return returnStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitTryCatchStatement(TryCatchStatement tryCatchStatement, VariableStatusInfo data)
+ {
+ //The needs special treatment in the analyser itself
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitBreakStatement(BreakStatement breakStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitContinueStatement(ContinueStatement continueStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitGotoStatement(GotoStatement gotoStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitGotoCaseStatement(GotoCaseStatement gotoCaseStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitGotoDefaultStatement(GotoDefaultStatement gotoDefaultStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitLabelStatement(LabelStatement labelStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitUnsafeStatement(UnsafeStatement unsafeStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitLockStatement(LockStatement lockStatement, VariableStatusInfo data)
+ {
+ var expressionResult = lockStatement.Expression.AcceptVisitor(this, data);
+ if (expressionResult.ThrowsException)
+ return expressionResult;
+
+ if (expressionResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return VisitorResult.ForException(expressionResult.Variables);
+ }
+
+ var identifier = CSharpUtil.GetInnerMostExpression(lockStatement.Expression) as IdentifierExpression;
+ if (identifier != null) {
+ var identifierValue = expressionResult.Variables [identifier.Identifier];
+ if (identifierValue != NullValueStatus.CapturedUnknown) {
+ var newVariables = expressionResult.Variables.Clone();
+ analysis.SetLocalVariableValue(newVariables, identifier, NullValueStatus.DefinitelyNotNull);
+
+ return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
+ }
+ }
+
+ return VisitorResult.ForValue(expressionResult.Variables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitThrowStatement(ThrowStatement throwStatement, VariableStatusInfo data)
+ {
+ if (throwStatement.Expression.IsNull)
+ return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull);
+ return throwStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement, VariableStatusInfo data)
+ {
+ return yieldReturnStatement.Expression.AcceptVisitor(this, data);
+ }
+
+ public override VisitorResult VisitCheckedStatement(CheckedStatement checkedStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitUncheckedStatement(UncheckedStatement uncheckedStatement, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ void RegisterExpressionResult(Expression expression, NullValueStatus expressionResult)
+ {
+ NullValueStatus oldStatus;
+ if (analysis.expressionResult.TryGetValue(expression, out oldStatus)) {
+ analysis.expressionResult[expression] = VariableStatusInfo.CombineStatus(analysis.expressionResult[expression], expressionResult);
+ }
+ else {
+ analysis.expressionResult[expression] = expressionResult;
+ }
+ }
+
+ VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, NullValueStatus expressionResult) {
+ RegisterExpressionResult(expression, expressionResult);
+
+ return VisitorResult.ForValue(dataAfterExpression, expressionResult);
+ }
+
+ VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, bool expressionResult) {
+ RegisterExpressionResult(expression, NullValueStatus.DefinitelyNotNull);
+
+ return VisitorResult.ForBoolValue(dataAfterExpression, expressionResult);
+ }
+
+ VisitorResult HandleExpressionResult(Expression expression, VisitorResult result) {
+ RegisterExpressionResult(expression, result.NullableReturnResult);
+
+ return result;
+ }
+
+ public override VisitorResult VisitAssignmentExpression(AssignmentExpression assignmentExpression, VariableStatusInfo data)
+ {
+ var tentativeResult = assignmentExpression.Left.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return HandleExpressionResult(assignmentExpression, tentativeResult);
+ tentativeResult = assignmentExpression.Right.AcceptVisitor(this, tentativeResult.Variables);
+ if (tentativeResult.ThrowsException)
+ return HandleExpressionResult(assignmentExpression, tentativeResult);
+
+ var leftIdentifier = assignmentExpression.Left as IdentifierExpression;
+ if (leftIdentifier != null) {
+ var resolveResult = analysis.context.Resolve(leftIdentifier);
+ if (resolveResult.IsError) {
+ return HandleExpressionResult(assignmentExpression, data, NullValueStatus.Error);
+ }
+
+ if (resolveResult is LocalResolveResult) {
+ var result = new VisitorResult();
+ result.NullableReturnResult = tentativeResult.NullableReturnResult;
+ result.Variables = tentativeResult.Variables.Clone();
+ var oldValue = result.Variables [leftIdentifier.Identifier];
+
+ if (assignmentExpression.Operator == AssignmentOperatorType.Assign ||
+ oldValue == NullValueStatus.Unassigned ||
+ oldValue == NullValueStatus.DefinitelyNotNull ||
+ tentativeResult.NullableReturnResult == NullValueStatus.Error ||
+ tentativeResult.NullableReturnResult == NullValueStatus.Unknown) {
+ analysis.SetLocalVariableValue(result.Variables, leftIdentifier, tentativeResult.NullableReturnResult);
+ } else {
+ if (oldValue == NullValueStatus.DefinitelyNull) {
+ //Do nothing --it'll remain null
+ } else {
+ analysis.SetLocalVariableValue(result.Variables, leftIdentifier, NullValueStatus.PotentiallyNull);
+ }
+ }
+
+ return HandleExpressionResult(assignmentExpression, result);
+ }
+ }
+
+ return HandleExpressionResult(assignmentExpression, tentativeResult);
+ }
+
+ public override VisitorResult VisitIdentifierExpression(IdentifierExpression identifierExpression, VariableStatusInfo data)
+ {
+ var resolveResult = analysis.context.Resolve(identifierExpression);
+ if (resolveResult.IsError) {
+ return HandleExpressionResult(identifierExpression, data, NullValueStatus.Error);
+ }
+ var local = resolveResult as LocalResolveResult;
+ if (local != null) {
+ var value = data [local.Variable.Name];
+ if (value == NullValueStatus.CapturedUnknown)
+ value = NullValueStatus.Unknown;
+ return HandleExpressionResult(identifierExpression, data, value);
+ }
+ if (resolveResult.IsCompileTimeConstant) {
+ object value = resolveResult.ConstantValue;
+ if (value == null) {
+ return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNull);
+ }
+ var boolValue = value as bool?;
+ if (boolValue != null) {
+ return VisitorResult.ForBoolValue(data, (bool)boolValue);
+ }
+ return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ var memberResolveResult = resolveResult as MemberResolveResult;
+
+ var returnValue = GetFieldReturnValue(memberResolveResult, data);
+
+ return HandleExpressionResult(identifierExpression, data, returnValue);
+ }
+
+ public override VisitorResult VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, VariableStatusInfo data)
+ {
+ var resolveResult = analysis.context.Resolve(defaultValueExpression);
+ if (resolveResult.IsError) {
+ return HandleExpressionResult(defaultValueExpression, data, NullValueStatus.Unknown);
+ }
+
+ Debug.Assert(resolveResult.IsCompileTimeConstant);
+
+ var status = resolveResult.ConstantValue == null && resolveResult.Type.IsReferenceType != false ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
+ return HandleExpressionResult(defaultValueExpression, data, status);
+ }
+
+ public override VisitorResult VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(nullReferenceExpression, data, NullValueStatus.DefinitelyNull);
+ }
+
+ public override VisitorResult VisitPrimitiveExpression(PrimitiveExpression primitiveExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(primitiveExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(parenthesizedExpression, parenthesizedExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitConditionalExpression(ConditionalExpression conditionalExpression, VariableStatusInfo data)
+ {
+ var tentativeBaseResult = conditionalExpression.Condition.AcceptVisitor(this, data);
+ if (tentativeBaseResult.ThrowsException)
+ return HandleExpressionResult(conditionalExpression, tentativeBaseResult);
+
+ var conditionResolveResult = analysis.context.Resolve(conditionalExpression.Condition);
+
+ if (tentativeBaseResult.KnownBoolResult == true || true.Equals(conditionResolveResult.ConstantValue)) {
+ return HandleExpressionResult(conditionalExpression, conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables));
+ }
+ if (tentativeBaseResult.KnownBoolResult == false || false.Equals(conditionResolveResult.ConstantValue)) {
+ return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables));
+ }
+
+ //No known bool result
+ var trueCaseResult = conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables);
+ if (trueCaseResult.ThrowsException) {
+ //We know that the true case will never be completed, then the right case is the only possible route.
+ return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables));
+ }
+ var falseCaseResult = conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables);
+ if (falseCaseResult.ThrowsException) {
+ return HandleExpressionResult(conditionalExpression, trueCaseResult.Variables, true);
+ }
+
+ return HandleExpressionResult(conditionalExpression, VisitorResult.OrOperation(trueCaseResult, falseCaseResult));
+ }
+
+ public override VisitorResult VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ //Let's not evaluate the sides just yet because of ??, && and ||
+
+ //We'll register the results here (with HandleExpressionResult)
+ //so each Visit*Expression won't have to do it itself
+ switch (binaryOperatorExpression.Operator) {
+ case BinaryOperatorType.ConditionalAnd:
+ return HandleExpressionResult(binaryOperatorExpression, VisitConditionalAndExpression(binaryOperatorExpression, data));
+ case BinaryOperatorType.ConditionalOr:
+ return HandleExpressionResult(binaryOperatorExpression, VisitConditionalOrExpression(binaryOperatorExpression, data));
+ case BinaryOperatorType.NullCoalescing:
+ return HandleExpressionResult(binaryOperatorExpression, VisitNullCoalescing(binaryOperatorExpression, data));
+ case BinaryOperatorType.Equality:
+ return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data));
+ case BinaryOperatorType.InEquality:
+ return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data).Negated);
+ default:
+ return HandleExpressionResult(binaryOperatorExpression, VisitOtherBinaryExpression(binaryOperatorExpression, data));
+ }
+ }
+
+ VisitorResult VisitOtherBinaryExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ if (leftTentativeResult.ThrowsException)
+ return leftTentativeResult;
+ var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, leftTentativeResult.Variables);
+ if (rightTentativeResult.ThrowsException)
+ return rightTentativeResult;
+
+ //TODO: Assuming operators are not overloaded by users
+ // (or, if they are, that they retain similar behavior to the default ones)
+
+ switch (binaryOperatorExpression.Operator) {
+ case BinaryOperatorType.LessThan:
+ case BinaryOperatorType.GreaterThan:
+ //Operations < and > with nulls always return false
+ //Those same operations will other values may or may not return false
+ if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull &&
+ rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
+ }
+ //We don't know what the value is, but we know that both true and false are != null.
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
+ case BinaryOperatorType.LessThanOrEqual:
+ case BinaryOperatorType.GreaterThanOrEqual:
+ if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
+ return VisitorResult.ForBoolValue(rightTentativeResult.Variables, true);
+ if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull)
+ return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
+ } else if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) {
+ if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
+ return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
+ }
+
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown);
+ default:
+ //Anything else: null + anything == anything + null == null.
+ //not null + not null = not null
+ if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull);
+ }
+ if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) {
+ if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull);
+ if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull)
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
+ }
+
+ return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown);
+ }
+ }
+
+ VisitorResult WithVariableValue(VisitorResult result, IdentifierExpression identifier, bool isNull)
+ {
+ var localVariableResult = analysis.context.Resolve(identifier) as LocalResolveResult;
+ if (localVariableResult != null) {
+ result.ConditionalBranchInfo.TrueResultVariableNullStates[identifier.Identifier] = isNull;
+ if (isNull) {
+ result.ConditionalBranchInfo.FalseResultVariableNullStates[identifier.Identifier] = false;
+ }
+ }
+ return result;
+ }
+
+ VisitorResult VisitEquality(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ //TODO: Should this check for user operators?
+
+ var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ if (tentativeLeftResult.ThrowsException)
+ return tentativeLeftResult;
+ var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, tentativeLeftResult.Variables);
+ if (tentativeRightResult.ThrowsException)
+ return tentativeRightResult;
+
+ if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == tentativeRightResult.KnownBoolResult) {
+ return VisitorResult.ForBoolValue(tentativeRightResult.Variables, true);
+ }
+
+ if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == !tentativeRightResult.KnownBoolResult) {
+ return VisitorResult.ForBoolValue(tentativeRightResult.Variables, false);
+ }
+
+ if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) {
+ if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) {
+ if (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull || tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return VisitorResult.ForBoolValue(tentativeRightResult.Variables, tentativeLeftResult.NullableReturnResult == tentativeRightResult.NullableReturnResult);
+ }
+ }
+ }
+
+ var result = new VisitorResult();
+ result.Variables = tentativeRightResult.Variables;
+ result.NullableReturnResult = NullValueStatus.Unknown;
+ result.ConditionalBranchInfo = new ConditionalBranchInfo();
+
+ if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) {
+ var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression;
+
+ if (identifier != null) {
+ bool isNull = (tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull);
+
+ WithVariableValue(result, identifier, isNull);
+ }
+ }
+
+ if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) {
+ var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Right) as IdentifierExpression;
+
+ if (identifier != null) {
+ bool isNull = (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull);
+
+ WithVariableValue(result, identifier, isNull);
+ }
+ }
+
+ return result;
+ }
+
+ VisitorResult VisitConditionalAndExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ if (tentativeLeftResult.KnownBoolResult == false || tentativeLeftResult.ThrowsException) {
+ return tentativeLeftResult;
+ }
+
+ var truePath = tentativeLeftResult.TruePathVariables;
+ var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, truePath);
+ if (tentativeRightResult.ThrowsException) {
+ //If the true path throws an exception, then the only way for the expression to complete
+ //successfully is if the left expression is false
+ return VisitorResult.ForBoolValue(tentativeLeftResult.FalsePathVariables, false);
+ }
+
+ return VisitorResult.AndOperation(tentativeLeftResult, tentativeRightResult);
+ }
+
+ VisitorResult VisitConditionalOrExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ if (tentativeLeftResult.KnownBoolResult == true || tentativeLeftResult.ThrowsException) {
+ return tentativeLeftResult;
+ }
+
+ var falsePath = tentativeLeftResult.FalsePathVariables;
+ var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, falsePath);
+ if (tentativeRightResult.ThrowsException) {
+ //If the false path throws an exception, then the only way for the expression to complete
+ //successfully is if the left expression is true
+ return VisitorResult.ForBoolValue(tentativeLeftResult.TruePathVariables, true);
+ }
+
+ return VisitorResult.OrOperation(tentativeLeftResult, tentativeRightResult);
+ }
+
+ VisitorResult VisitNullCoalescing(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
+ {
+ var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
+ if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull || leftTentativeResult.ThrowsException) {
+ return leftTentativeResult;
+ }
+
+ //If the right side is found, then the left side is known to be null
+ var newData = leftTentativeResult.Variables;
+ var leftIdentifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression;
+ if (leftIdentifier != null) {
+ newData = newData.Clone();
+ analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNull);
+ }
+
+ var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, newData);
+ if (rightTentativeResult.ThrowsException) {
+ //This means the left expression was not null all along (or else the expression will throw an exception)
+
+ if (leftIdentifier != null) {
+ newData = newData.Clone();
+ analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNotNull);
+ return VisitorResult.ForValue(newData, NullValueStatus.DefinitelyNotNull);
+ }
+
+ return VisitorResult.ForValue(leftTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
+ }
+
+ var mergedVariables = rightTentativeResult.Variables;
+ var nullValue = rightTentativeResult.NullableReturnResult;
+
+ if (leftTentativeResult.NullableReturnResult != NullValueStatus.DefinitelyNull) {
+ mergedVariables = mergedVariables.Clone();
+ mergedVariables.ReceiveIncoming(leftTentativeResult.Variables);
+ if (nullValue == NullValueStatus.DefinitelyNull) {
+ nullValue = NullValueStatus.PotentiallyNull;
+ }
+ }
+
+ return VisitorResult.ForValue(mergedVariables, nullValue);
+ }
+
+ public override VisitorResult VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, VariableStatusInfo data)
+ {
+ //TODO: Again, what to do when overloaded operators are found?
+
+ var tentativeResult = unaryOperatorExpression.Expression.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return HandleExpressionResult(unaryOperatorExpression, tentativeResult);
+
+ if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) {
+ return HandleExpressionResult(unaryOperatorExpression, tentativeResult.Negated);
+ }
+ return HandleExpressionResult(unaryOperatorExpression, tentativeResult);
+ }
+
+ public override VisitorResult VisitInvocationExpression(InvocationExpression invocationExpression, VariableStatusInfo data)
+ {
+ //TODO: Handle some common methods such as string.IsNullOrEmpty
+
+ var targetResult = invocationExpression.Target.AcceptVisitor(this, data);
+ if (targetResult.ThrowsException)
+ return HandleExpressionResult(invocationExpression, targetResult);
+
+ data = targetResult.Variables;
+
+ var methodResolveResult = analysis.context.Resolve(invocationExpression) as CSharpInvocationResolveResult;
+
+ List parameterResults = new List();
+
+ foreach (var argumentToHandle in invocationExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) {
+ var argument = argumentToHandle.argument;
+ var parameterIndex = argumentToHandle.parameterIndex;
+
+ var result = argument.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return HandleExpressionResult(invocationExpression, result);
+ parameterResults.Add(result);
+
+ var namedArgument = argument as NamedArgumentExpression;
+
+ var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression;
+ if (directionExpression != null && methodResolveResult != null) {
+ var identifier = directionExpression.Expression as IdentifierExpression;
+ if (identifier != null) {
+ //out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return)
+ var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
+ if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
+ data = data.Clone();
+
+ FixParameter(argument, methodResolveResult.Member.Parameters, parameterIndex, identifier, data);
+ }
+ }
+
+
+ continue;
+ }
+
+ data = result.Variables;
+ }
+
+ var identifierExpression = CSharpUtil.GetInnerMostExpression(invocationExpression.Target) as IdentifierExpression;
+ if (identifierExpression != null) {
+ if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return HandleExpressionResult(invocationExpression, VisitorResult.ForException(data));
+ }
+
+ var descendentIdentifiers = invocationExpression.Arguments.SelectMany(argument => argument.DescendantsAndSelf).OfType();
+ if (!descendentIdentifiers.Any(identifier => identifier.Identifier == identifierExpression.Identifier)) {
+ //TODO: We can make this check better (see VisitIndexerExpression for more details)
+ data = data.Clone();
+ analysis.SetLocalVariableValue(data, identifierExpression, NullValueStatus.DefinitelyNotNull);
+ }
+ }
+
+ return HandleExpressionResult(invocationExpression, GetMethodVisitorResult(methodResolveResult, data, parameterResults));
+ }
+
+ static VisitorResult GetMethodVisitorResult(CSharpInvocationResolveResult methodResolveResult, VariableStatusInfo data, List parameterResults)
+ {
+ if (methodResolveResult == null)
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+
+ var method = methodResolveResult.Member as IMethod;
+ if (method != null) {
+ if (method.GetAttribute(new FullTypeName(AnnotationNames.AssertionMethodAttribute)) != null) {
+ var assertionParameters = method.Parameters.Select((parameter, index) => new { index, parameter })
+ .Select(parameter => new { parameter.index, parameter.parameter, attributes = parameter.parameter.Attributes.Where(attribute => attribute.AttributeType.FullName == AnnotationNames.AssertionConditionAttribute).ToList() })
+ .Where(parameter => parameter.attributes.Count() == 1)
+ .Select(parameter => new { parameter.index, parameter.parameter, attribute = parameter.attributes[0] })
+ .ToList();
+
+ //Unclear what should be done if there are multiple assertion conditions
+ if (assertionParameters.Count() == 1) {
+ Debug.Assert(methodResolveResult.Arguments.Count == parameterResults.Count);
+
+ var assertionParameter = assertionParameters [0];
+ VisitorResult assertionParameterResult = null;
+
+ object intendedResult = true;
+ var positionalArgument = assertionParameter.attribute.PositionalArguments.FirstOrDefault() as MemberResolveResult;
+ if (positionalArgument != null && positionalArgument.Type.FullName == AnnotationNames.AssertionConditionTypeAttribute) {
+ switch (positionalArgument.Member.FullName) {
+ case AnnotationNames.AssertionConditionTypeIsTrue:
+ intendedResult = true;
+ break;
+ case AnnotationNames.AssertionConditionTypeIsFalse:
+ intendedResult = false;
+ break;
+ case AnnotationNames.AssertionConditionTypeIsNull:
+ intendedResult = null;
+ break;
+ case AnnotationNames.AssertionConditionTypeIsNotNull:
+ intendedResult = "";
+ break;
+ }
+ }
+
+ int parameterIndex = assertionParameter.index;
+ if (assertionParameter.index < methodResolveResult.Arguments.Count && !(methodResolveResult.Arguments [assertionParameter.index] is NamedArgumentResolveResult)) {
+ //Use index
+ assertionParameterResult = parameterResults [assertionParameter.index];
+ } else {
+ //Use named argument
+ int? nameIndex = methodResolveResult.Arguments.Select((argument, index) => new { argument, index})
+ .Where(argument => {
+ var namedArgument = argument.argument as NamedArgumentResolveResult;
+ return namedArgument != null && namedArgument.ParameterName == assertionParameter.parameter.Name;
+ }).Select(argument => (int?)argument.index).FirstOrDefault();
+
+ if (nameIndex != null) {
+ parameterIndex = nameIndex.Value;
+ assertionParameterResult = parameterResults [nameIndex.Value];
+ } else if (assertionParameter.parameter.IsOptional) {
+ //Try to use default value
+
+ if (intendedResult is string) {
+ if (assertionParameter.parameter.ConstantValue == null) {
+ return VisitorResult.ForException(data);
+ }
+ } else {
+ if (!object.Equals(assertionParameter.parameter.ConstantValue, intendedResult)) {
+ return VisitorResult.ForException(data);
+ }
+ }
+ } else {
+ //The parameter was not specified, yet it is not optional?
+ return VisitorResult.ForException(data);
+ }
+ }
+
+ //Now check assertion
+ if (assertionParameterResult != null) {
+ if (intendedResult is bool) {
+ if (assertionParameterResult.KnownBoolResult == !(bool)intendedResult) {
+ return VisitorResult.ForException(data);
+ }
+
+ data = (bool)intendedResult ? assertionParameterResult.TruePathVariables : assertionParameterResult.FalsePathVariables;
+ } else {
+ bool shouldBeNull = intendedResult == null;
+
+ if (assertionParameterResult.NullableReturnResult == (shouldBeNull ? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull)) {
+ return VisitorResult.ForException(data);
+ }
+
+ var parameterResolveResult = methodResolveResult.Arguments [parameterIndex];
+
+ LocalResolveResult localVariableResult = null;
+
+ var conversionResolveResult = parameterResolveResult as ConversionResolveResult;
+ if (conversionResolveResult != null) {
+ if (!IsTypeNullable(conversionResolveResult.Type)) {
+ if (intendedResult == null) {
+ return VisitorResult.ForException(data);
+ }
+ } else {
+ localVariableResult = conversionResolveResult.Input as LocalResolveResult;
+ }
+ } else {
+ localVariableResult = parameterResolveResult as LocalResolveResult;
+ }
+
+ if (localVariableResult != null && data[localVariableResult.Variable.Name] != NullValueStatus.CapturedUnknown) {
+ data = data.Clone();
+ data [localVariableResult.Variable.Name] = shouldBeNull ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ bool isNullable = IsTypeNullable(methodResolveResult.Type);
+ if (!isNullable) {
+ return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ if (method != null)
+ return VisitorResult.ForValue(data, GetNullableStatus(method));
+
+ return VisitorResult.ForValue(data, GetNullableStatus(methodResolveResult.TargetResult.Type.GetDefinition()));
+ }
+
+ static NullValueStatus GetNullableStatus(IEntity entity)
+ {
+ if (entity.DeclaringType != null && entity.DeclaringType.Kind == TypeKind.Delegate) {
+ //Handle Delegate.Invoke method
+ return GetNullableStatus(entity.DeclaringTypeDefinition);
+ }
+
+ return GetNullableStatus(fullTypeName => entity.GetAttribute(new FullTypeName(fullTypeName)));
+ }
+
+ static NullValueStatus GetNullableStatus(IParameter parameter)
+ {
+ return GetNullableStatus(fullTypeName => parameter.Attributes.FirstOrDefault(attribute => attribute.AttributeType.FullName == fullTypeName));
+ }
+
+ static NullValueStatus GetNullableStatus(Func attributeGetter)
+ {
+ if (attributeGetter(AnnotationNames.NotNullAttribute) != null) {
+ return NullValueStatus.DefinitelyNotNull;
+ }
+ if (attributeGetter(AnnotationNames.CanBeNullAttribute) != null) {
+ return NullValueStatus.PotentiallyNull;
+ }
+ return NullValueStatus.Unknown;
+ }
+
+ public override VisitorResult VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, VariableStatusInfo data)
+ {
+ var targetResult = memberReferenceExpression.Target.AcceptVisitor(this, data);
+ if (targetResult.ThrowsException)
+ return HandleExpressionResult(memberReferenceExpression, targetResult);
+
+ var variables = targetResult.Variables;
+
+ var memberResolveResult = analysis.context.Resolve(memberReferenceExpression) as MemberResolveResult;
+
+ var targetIdentifier = CSharpUtil.GetInnerMostExpression(memberReferenceExpression.Target) as IdentifierExpression;
+ if (targetIdentifier != null) {
+ if (memberResolveResult == null) {
+ var invocation = memberReferenceExpression.Parent as InvocationExpression;
+ if (invocation != null) {
+ memberResolveResult = analysis.context.Resolve(invocation) as MemberResolveResult;
+ }
+ }
+
+ if (memberResolveResult != null && memberResolveResult.Member.FullName != "System.Nullable.HasValue") {
+ var method = memberResolveResult.Member as IMethod;
+ if (method == null || !method.IsExtensionMethod) {
+ if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ return HandleExpressionResult(memberReferenceExpression, VisitorResult.ForException(variables));
+ }
+ if (variables [targetIdentifier.Identifier] != NullValueStatus.CapturedUnknown) {
+ variables = variables.Clone();
+ analysis.SetLocalVariableValue(variables, targetIdentifier, NullValueStatus.DefinitelyNotNull);
+ }
+ }
+ }
+ }
+
+ var returnValue = GetFieldReturnValue(memberResolveResult, data);
+ return HandleExpressionResult(memberReferenceExpression, variables, returnValue);
+ }
+
+ static NullValueStatus GetFieldReturnValue(MemberResolveResult memberResolveResult, VariableStatusInfo data)
+ {
+ bool isNullable = memberResolveResult == null || IsTypeNullable(memberResolveResult.Type);
+ if (!isNullable) {
+ return NullValueStatus.DefinitelyNotNull;
+ }
+
+ if (memberResolveResult != null) {
+ return GetNullableStatus(memberResolveResult.Member);
+ }
+
+ return NullValueStatus.Unknown;
+ }
+
+ public override VisitorResult VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(typeReferenceExpression, data, NullValueStatus.Unknown);
+
+ }
+
+ void FixParameter(Expression argument, IList parameters, int parameterIndex, IdentifierExpression identifier, VariableStatusInfo data)
+ {
+ NullValueStatus newValue = NullValueStatus.Unknown;
+ if (argument is NamedArgumentExpression) {
+ var namedResolveResult = analysis.context.Resolve(argument) as NamedArgumentResolveResult;
+ if (namedResolveResult != null) {
+ newValue = GetNullableStatus(namedResolveResult.Parameter);
+ }
+ }
+ else {
+ var parameter = parameters[parameterIndex];
+ newValue = GetNullableStatus(parameter);
+ }
+ analysis.SetLocalVariableValue(data, identifier, newValue);
+ }
+
+ public override VisitorResult VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, VariableStatusInfo data)
+ {
+ foreach (var argumentToHandle in objectCreateExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) {
+ var argument = argumentToHandle.argument;
+ var parameterIndex = argumentToHandle.parameterIndex;
+
+ var namedArgument = argument as NamedArgumentExpression;
+
+ var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression;
+ if (directionExpression != null) {
+ var identifier = directionExpression.Expression as IdentifierExpression;
+ if (identifier != null && data [identifier.Identifier] != NullValueStatus.CapturedUnknown) {
+ //out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return)
+ data = data.Clone();
+
+ var constructorResolveResult = analysis.context.Resolve(objectCreateExpression) as CSharpInvocationResolveResult;
+ if (constructorResolveResult != null)
+ FixParameter(argument, constructorResolveResult.Member.Parameters, parameterIndex, identifier, data);
+ }
+ continue;
+ }
+
+ var argumentResult = argument.AcceptVisitor(this, data);
+ if (argumentResult.ThrowsException)
+ return argumentResult;
+
+ data = argumentResult.Variables;
+ }
+
+ //Constructors never return null
+ return HandleExpressionResult(objectCreateExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitArrayCreateExpression(ArrayCreateExpression arrayCreateExpression, VariableStatusInfo data)
+ {
+ foreach (var argument in arrayCreateExpression.Arguments) {
+ var result = argument.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables.Clone();
+ }
+
+ if (arrayCreateExpression.Initializer.IsNull) {
+ return HandleExpressionResult(arrayCreateExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ return HandleExpressionResult(arrayCreateExpression, arrayCreateExpression.Initializer.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, VariableStatusInfo data)
+ {
+ if (arrayInitializerExpression.IsSingleElement) {
+ return HandleExpressionResult(arrayInitializerExpression, arrayInitializerExpression.Elements.Single().AcceptVisitor(this, data));
+ }
+ if (!arrayInitializerExpression.Elements.Any()) {
+ //Empty array
+ return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForValue(data, NullValueStatus.Unknown));
+ }
+
+ NullValueStatus enumeratedValue = NullValueStatus.UnreachableOrInexistent;
+ foreach (var element in arrayInitializerExpression.Elements) {
+ var result = element.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables.Clone();
+ enumeratedValue = VariableStatusInfo.CombineStatus(enumeratedValue, result.NullableReturnResult);
+
+ }
+ return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForEnumeratedValue(data, enumeratedValue));
+ }
+
+ public override VisitorResult VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression, VariableStatusInfo data)
+ {
+ foreach (var initializer in anonymousTypeCreateExpression.Initializers) {
+ var result = initializer.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables;
+ }
+
+ return HandleExpressionResult(anonymousTypeCreateExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitLambdaExpression(LambdaExpression lambdaExpression, VariableStatusInfo data)
+ {
+ var newData = data.Clone();
+
+ var identifiers = lambdaExpression.Descendants.OfType();
+ foreach (var identifier in identifiers) {
+ //Check if it is in a "change-null-state" context
+ //For instance, x++ does not change the null state
+ //but `x = y` does.
+ if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) {
+ var parent = (AssignmentExpression)identifier.Parent;
+ if (parent.Operator != AssignmentOperatorType.Assign) {
+ continue;
+ }
+ } else {
+ //No other context matters
+ //Captured variables are never passed by reference (out/ref)
+ continue;
+ }
+
+ //At this point, we know there's a good chance the variable has been changed
+ var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
+ if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
+ analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown);
+ }
+ }
+
+ //The lambda itself is known not to be null
+ return HandleExpressionResult(lambdaExpression, newData, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, VariableStatusInfo data)
+ {
+ var newData = data.Clone();
+
+ var identifiers = anonymousMethodExpression.Descendants.OfType();
+ foreach (var identifier in identifiers) {
+ //Check if it is in a "change-null-state" context
+ //For instance, x++ does not change the null state
+ //but `x = y` does.
+ if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) {
+ var parent = (AssignmentExpression)identifier.Parent;
+ if (parent.Operator != AssignmentOperatorType.Assign) {
+ continue;
+ }
+ } else {
+ //No other context matters
+ //Captured variables are never passed by reference (out/ref)
+ continue;
+ }
+
+ //At this point, we know there's a good chance the variable has been changed
+ var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
+ if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
+ analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown);
+ }
+ }
+
+ //The anonymous method itself is known not to be null
+ return HandleExpressionResult(anonymousMethodExpression, newData, NullValueStatus.DefinitelyNotNull);
+ }
+
+
+ public override VisitorResult VisitNamedExpression(NamedExpression namedExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(namedExpression, namedExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitAsExpression(AsExpression asExpression, VariableStatusInfo data)
+ {
+ var tentativeResult = asExpression.Expression.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return tentativeResult;
+
+ NullValueStatus result;
+ if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ result = NullValueStatus.DefinitelyNull;
+ } else {
+ var asResolveResult = analysis.context.Resolve(asExpression) as CastResolveResult;
+ if (asResolveResult == null ||
+ asResolveResult.IsError ||
+ asResolveResult.Input.Type.Kind == TypeKind.Unknown ||
+ asResolveResult.Type.Kind == TypeKind.Unknown) {
+
+ result = NullValueStatus.Unknown;
+ } else {
+ var conversion = new CSharpConversions(analysis.context.Compilation);
+ var foundConversion = conversion.ExplicitConversion(asResolveResult.Input.Type, asResolveResult.Type);
+
+ if (foundConversion == Conversion.None) {
+ result = NullValueStatus.DefinitelyNull;
+ } else if (foundConversion == Conversion.IdentityConversion) {
+ result = tentativeResult.NullableReturnResult;
+ } else {
+ result = NullValueStatus.PotentiallyNull;
+ }
+ }
+ }
+ return HandleExpressionResult(asExpression, tentativeResult.Variables, result);
+ }
+
+ public override VisitorResult VisitCastExpression(CastExpression castExpression, VariableStatusInfo data)
+ {
+ var tentativeResult = castExpression.Expression.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return tentativeResult;
+
+ NullValueStatus result;
+ if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
+ result = NullValueStatus.DefinitelyNull;
+ } else {
+ result = NullValueStatus.Unknown;
+ }
+
+ VariableStatusInfo variables = tentativeResult.Variables;
+
+ var resolveResult = analysis.context.Resolve(castExpression) as CastResolveResult;
+ if (resolveResult != null && !IsTypeNullable(resolveResult.Type)) {
+ if (result == NullValueStatus.DefinitelyNull) {
+ return HandleExpressionResult(castExpression, VisitorResult.ForException(tentativeResult.Variables));
+ }
+
+ var identifierExpression = CSharpUtil.GetInnerMostExpression(castExpression.Expression) as IdentifierExpression;
+ if (identifierExpression != null) {
+ var currentValue = variables [identifierExpression.Identifier];
+ if (currentValue != NullValueStatus.CapturedUnknown &&
+ currentValue != NullValueStatus.UnreachableOrInexistent &&
+ currentValue != NullValueStatus.DefinitelyNotNull) {
+ //DefinitelyNotNull is included in this list because if that's the status
+ // then we don't need to change anything
+
+ variables = variables.Clone();
+ variables [identifierExpression.Identifier] = NullValueStatus.DefinitelyNotNull;
+ }
+ }
+
+ result = NullValueStatus.DefinitelyNotNull;
+ }
+
+ return HandleExpressionResult(castExpression, variables, result);
+ }
+
+ public override VisitorResult VisitIsExpression(IsExpression isExpression, VariableStatusInfo data)
+ {
+ var tentativeResult = isExpression.Expression.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return tentativeResult;
+
+ //TODO: Consider, for instance: new X() is X. The result is known to be true, so we can use KnownBoolValue
+ return HandleExpressionResult(isExpression, tentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitDirectionExpression(DirectionExpression directionExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(directionExpression, directionExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitCheckedExpression(CheckedExpression checkedExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(checkedExpression, checkedExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitUncheckedExpression(UncheckedExpression uncheckedExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(uncheckedExpression, uncheckedExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(thisReferenceExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitIndexerExpression(IndexerExpression indexerExpression, VariableStatusInfo data)
+ {
+ var tentativeResult = indexerExpression.Target.AcceptVisitor(this, data);
+ if (tentativeResult.ThrowsException)
+ return tentativeResult;
+
+ data = tentativeResult.Variables;
+
+ foreach (var argument in indexerExpression.Arguments) {
+ var result = argument.AcceptVisitor(this, data);
+ if (result.ThrowsException)
+ return result;
+ data = result.Variables.Clone();
+ }
+
+ IdentifierExpression targetAsIdentifier = CSharpUtil.GetInnerMostExpression(indexerExpression.Target) as IdentifierExpression;
+ if (targetAsIdentifier != null) {
+ if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
+ return HandleExpressionResult(indexerExpression, VisitorResult.ForException(data));
+
+ //If this doesn't cause an exception, then the target is not null
+ //But we won't set it if it has been changed
+ var descendentIdentifiers = indexerExpression.Arguments
+ .SelectMany(argument => argument.DescendantsAndSelf).OfType();
+ if (!descendentIdentifiers.Any(identifier => identifier.Identifier == targetAsIdentifier.Identifier)) {
+ //TODO: this check might be improved to include more legitimate cases
+ //A good check will necessarily have to consider captured variables
+ data = data.Clone();
+ analysis.SetLocalVariableValue(data, targetAsIdentifier, NullValueStatus.DefinitelyNotNull);
+ }
+ }
+
+ var indexerResolveResult = analysis.context.Resolve(indexerExpression) as CSharpInvocationResolveResult;
+ bool isNullable = indexerResolveResult == null || IsTypeNullable(indexerResolveResult.Type);
+
+ var returnValue = isNullable ? NullValueStatus.Unknown : NullValueStatus.DefinitelyNotNull;
+ return HandleExpressionResult(indexerExpression, data, returnValue);
+ }
+
+ public override VisitorResult VisitBaseReferenceExpression(BaseReferenceExpression baseReferenceExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(baseReferenceExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitTypeOfExpression(TypeOfExpression typeOfExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(typeOfExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitSizeOfExpression(SizeOfExpression sizeOfExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(sizeOfExpression, data, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, VariableStatusInfo data)
+ {
+ var targetResult = pointerReferenceExpression.Target.AcceptVisitor(this, data);
+ if (targetResult.ThrowsException)
+ return targetResult;
+ return HandleExpressionResult(pointerReferenceExpression, targetResult.Variables, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitStackAllocExpression(StackAllocExpression stackAllocExpression, VariableStatusInfo data)
+ {
+ var countResult = stackAllocExpression.CountExpression.AcceptVisitor(this, data);
+ if (countResult.ThrowsException)
+ return countResult;
+ return HandleExpressionResult(stackAllocExpression, countResult.Variables, NullValueStatus.DefinitelyNotNull);
+ }
+
+ public override VisitorResult VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression, VariableStatusInfo data)
+ {
+ return HandleExpressionResult(namedArgumentExpression, namedArgumentExpression.Expression.AcceptVisitor(this, data));
+ }
+
+ public override VisitorResult VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression, VariableStatusInfo data)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override VisitorResult VisitQueryExpression(QueryExpression queryExpression, VariableStatusInfo data)
+ {
+ VariableStatusInfo outgoingData = data.Clone();
+ NullValueStatus? outgoingEnumeratedValue = null;
+ var clauses = queryExpression.Clauses.ToList();
+
+ var backtracingClauses = (from item in clauses.Select((clause, i) => new { clause, i })
+ where item.clause is QueryFromClause || item.clause is QueryJoinClause || item.clause is QueryContinuationClause
+ select item.i).ToList();
+
+ var beforeClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex,
+ clauseIndex => new VariableStatusInfo());
+ var afterClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex,
+ clauseIndex => new VariableStatusInfo());
+
+ VisitorResult lastValidResult = null;
+ int currentClauseIndex = 0;
+ for (;;) {
+ VisitorResult result = null;
+ QueryClause clause = null;
+ bool backtrack = false;
+
+ if (currentClauseIndex >= clauses.Count) {
+ backtrack = true;
+ } else {
+ clause = clauses [currentClauseIndex];
+ beforeClauseVariableStates [currentClauseIndex].ReceiveIncoming(data);
+ result = clause.AcceptVisitor(this, data);
+ data = result.Variables;
+ lastValidResult = result;
+ if (result.KnownBoolResult == false) {
+ backtrack = true;
+ }
+ if (result.ThrowsException) {
+ //Don't backtrack. Exceptions completely stop the query.
+ break;
+ }
+ else {
+ afterClauseVariableStates [currentClauseIndex].ReceiveIncoming(data);
+ }
+ }
+
+ if (backtrack) {
+ int? newIndex;
+ for (;;) {
+ newIndex = backtracingClauses.LastOrDefault(index => index < currentClauseIndex);
+ if (newIndex == null) {
+ //We've reached the end
+ break;
+ }
+
+ currentClauseIndex = (int)newIndex + 1;
+
+ if (!beforeClauseVariableStates[currentClauseIndex].ReceiveIncoming(lastValidResult.Variables)) {
+ newIndex = null;
+ break;
+ }
+ }
+
+ if (newIndex == null) {
+ break;
+ }
+
+ } else {
+ if (clause is QuerySelectClause) {
+ outgoingData.ReceiveIncoming(data);
+ if (outgoingEnumeratedValue == null)
+ outgoingEnumeratedValue = result.EnumeratedValueResult;
+ else
+ outgoingEnumeratedValue = VariableStatusInfo.CombineStatus(outgoingEnumeratedValue.Value, result.EnumeratedValueResult);
+ }
+
+ ++currentClauseIndex;
+ }
+ }
+
+ var finalData = new VariableStatusInfo();
+ var endingClauseIndices = from item in clauses.Select((clause, i) => new { clause, i })
+ let clause = item.clause
+ where clause is QueryFromClause ||
+ clause is QueryContinuationClause ||
+ clause is QueryJoinClause ||
+ clause is QuerySelectClause ||
+ clause is QueryWhereClause
+ select item.i;
+ foreach (var clauseIndex in endingClauseIndices) {
+ finalData.ReceiveIncoming(afterClauseVariableStates [clauseIndex]);
+ }
+
+ return VisitorResult.ForEnumeratedValue(finalData, outgoingEnumeratedValue ?? NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, VariableStatusInfo data)
+ {
+ return IntroduceVariableFromEnumeratedValue(queryContinuationClause.Identifier, queryContinuationClause.PrecedingQuery, data);
+ }
+
+ VisitorResult IntroduceVariableFromEnumeratedValue(string newVariable, Expression expression, VariableStatusInfo data)
+ {
+ var result = expression.AcceptVisitor(this, data);
+ var newVariables = result.Variables.Clone();
+ newVariables[newVariable] = result.EnumeratedValueResult;
+ return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQueryFromClause(QueryFromClause queryFromClause, VariableStatusInfo data)
+ {
+ return IntroduceVariableFromEnumeratedValue(queryFromClause.Identifier, queryFromClause.Expression, data);
+ }
+
+ public override VisitorResult VisitQueryJoinClause(QueryJoinClause queryJoinClause, VariableStatusInfo data)
+ {
+ //TODO: Check if this really works in weird edge-cases.
+ var tentativeResult = IntroduceVariableFromEnumeratedValue(queryJoinClause.JoinIdentifier, queryJoinClause.InExpression, data);
+ tentativeResult = queryJoinClause.OnExpression.AcceptVisitor(this, tentativeResult.Variables);
+ tentativeResult = queryJoinClause.EqualsExpression.AcceptVisitor(this, tentativeResult.Variables);
+
+ if (queryJoinClause.IsGroupJoin) {
+ var newVariables = tentativeResult.Variables.Clone();
+ analysis.SetLocalVariableValue(newVariables, queryJoinClause.IntoIdentifierToken, NullValueStatus.DefinitelyNotNull);
+ return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
+ }
+
+ return tentativeResult;
+ }
+
+ public override VisitorResult VisitQueryLetClause(QueryLetClause queryLetClause, VariableStatusInfo data)
+ {
+ var result = queryLetClause.Expression.AcceptVisitor(this, data);
+
+ string newVariable = queryLetClause.Identifier;
+ var newVariables = result.Variables.Clone();
+ newVariables [newVariable] = result.NullableReturnResult;
+
+ return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQuerySelectClause(QuerySelectClause querySelectClause, VariableStatusInfo data)
+ {
+ var result = querySelectClause.Expression.AcceptVisitor(this, data);
+
+ //The value of the expression in select becomes the "enumerated" value
+ return VisitorResult.ForEnumeratedValue(result.Variables, result.NullableReturnResult);
+ }
+
+ public override VisitorResult VisitQueryWhereClause(QueryWhereClause queryWhereClause, VariableStatusInfo data)
+ {
+ var result = queryWhereClause.Condition.AcceptVisitor(this, data);
+
+ return VisitorResult.ForEnumeratedValue(result.TruePathVariables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQueryOrderClause(QueryOrderClause queryOrderClause, VariableStatusInfo data)
+ {
+ foreach (var ordering in queryOrderClause.Orderings) {
+ data = ordering.AcceptVisitor(this, data).Variables;
+ }
+
+ return VisitorResult.ForValue(data, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQueryOrdering(QueryOrdering queryOrdering, VariableStatusInfo data)
+ {
+ return VisitorResult.ForValue(queryOrdering.Expression.AcceptVisitor(this, data).Variables, NullValueStatus.Unknown);
+ }
+
+ public override VisitorResult VisitQueryGroupClause(QueryGroupClause queryGroupClause, VariableStatusInfo data)
+ {
+ var projectionResult = queryGroupClause.Projection.AcceptVisitor(this, data);
+ data = projectionResult.Variables;
+ data = queryGroupClause.Key.AcceptVisitor(this, data).Variables;
+
+ return VisitorResult.ForEnumeratedValue(data, projectionResult.NullableReturnResult);
+ }
+ }
+ }
+}
+
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueStatus.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueStatus.cs
new file mode 100644
index 000000000..f6816dfd5
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/NullValueStatus.cs
@@ -0,0 +1,84 @@
+//
+// NullValueAnalysis.cs
+//
+// Author:
+// Luís Reis
+//
+// Copyright (c) 2013 Luís Reis
+//
+// 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;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ ///
+ /// Represents the null value status of a variable at a specific location.
+ ///
+ public enum NullValueStatus
+ {
+ ///
+ /// The value of the variable is unknown, possibly due to limitations
+ /// of the null value analysis.
+ ///
+ Unknown = 0,
+ ///
+ /// The value of the variable is unknown and even assigning it to a
+ /// value won't change its state, since it has been captured by a lambda
+ /// that may change it at any time (potentially even from a different thread).
+ /// Only going out of scope and creating a new variable may change the value
+ /// of this variable.
+ ///
+ CapturedUnknown,
+ ///
+ /// This variable is potentially unassigned.
+ ///
+ Unassigned,
+ ///
+ /// The value of the variable is provably null.
+ ///
+ DefinitelyNull,
+ ///
+ /// The value of the variable might or might not be null
+ ///
+ PotentiallyNull,
+ ///
+ /// The value of the variable is provably not null
+ ///
+ DefinitelyNotNull,
+ ///
+ /// The position of this node is unreachable, therefore the value
+ /// of the variable is not meaningful.
+ /// Alternatively, it might mean no local variable exists with the requested name.
+ ///
+ UnreachableOrInexistent,
+ ///
+ /// The analyser has encountered an error when attempting to find the value
+ /// of this variable.
+ ///
+ Error
+ }
+
+ public static class NullValueStatusExtensions
+ {
+ public static bool IsDefiniteValue (this NullValueStatus self) {
+ return self == NullValueStatus.DefinitelyNull || self == NullValueStatus.DefinitelyNotNull;
+ }
+ }
+}
+
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs
new file mode 100644
index 000000000..ce64f5f3b
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/ReachabilityAnalysis.cs
@@ -0,0 +1,209 @@
+// Copyright (c) 2010-2013 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.Threading;
+using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.CSharp.TypeSystem;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.CSharp;
+
+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();
+ RecursiveDetectorVisitor recursiveDetectorVisitor = null;
+
+ private ReachabilityAnalysis() {}
+
+ public static ReachabilityAnalysis Create(Statement statement, CSharpAstResolver resolver = null, RecursiveDetectorVisitor recursiveDetectorVisitor = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var cfgBuilder = new ControlFlowGraphBuilder();
+ var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, cancellationToken);
+ return Create(cfg, recursiveDetectorVisitor, cancellationToken);
+ }
+
+ internal static ReachabilityAnalysis Create(Statement statement, Func resolver, CSharpTypeResolveContext typeResolveContext, CancellationToken cancellationToken)
+ {
+ var cfgBuilder = new ControlFlowGraphBuilder();
+ var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, typeResolveContext, cancellationToken);
+ return Create(cfg, null, cancellationToken);
+ }
+
+ public static ReachabilityAnalysis Create(IList controlFlowGraph, RecursiveDetectorVisitor recursiveDetectorVisitor = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (controlFlowGraph == null)
+ throw new ArgumentNullException("controlFlowGraph");
+ ReachabilityAnalysis ra = new ReachabilityAnalysis();
+ ra.recursiveDetectorVisitor = recursiveDetectorVisitor;
+ // Analysing a null node can result in an empty control flow graph
+ if (controlFlowGraph.Count > 0) {
+ 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) {
+ if (node.PreviousStatement is LabelStatement) {
+ reachableStatements.Add(node.PreviousStatement);
+ }
+ reachableEndPoints.Add(node.PreviousStatement);
+ }
+ if (node.NextStatement != null) {
+ reachableStatements.Add(node.NextStatement);
+ if (IsRecursive(node.NextStatement)) {
+ return;
+ }
+ }
+ foreach (var edge in node.Outgoing) {
+ if (visitedNodes.Add(edge.To))
+ stack.Push(edge.To);
+ }
+ }
+
+ bool IsRecursive(Statement statement)
+ {
+ return recursiveDetectorVisitor != null && statement.AcceptVisitor(recursiveDetectorVisitor);
+ }
+
+ public IEnumerable ReachableStatements {
+ get { return reachableStatements; }
+ }
+
+ public bool IsReachable(Statement statement)
+ {
+ return reachableStatements.Contains(statement);
+ }
+
+ public bool IsEndpointReachable(Statement statement)
+ {
+ return reachableEndPoints.Contains(statement);
+ }
+
+ public class RecursiveDetectorVisitor : DepthFirstAstVisitor
+ {
+ public override bool VisitConditionalExpression(ConditionalExpression conditionalExpression)
+ {
+ if (conditionalExpression.Condition.AcceptVisitor(this))
+ return true;
+
+ if (!conditionalExpression.TrueExpression.AcceptVisitor(this))
+ return false;
+
+ return conditionalExpression.FalseExpression.AcceptVisitor(this);
+ }
+
+ public override bool VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
+ {
+ if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
+ return binaryOperatorExpression.Left.AcceptVisitor(this);
+ }
+ return base.VisitBinaryOperatorExpression(binaryOperatorExpression);
+ }
+
+ public override bool VisitIfElseStatement(IfElseStatement ifElseStatement)
+ {
+ if (ifElseStatement.Condition.AcceptVisitor(this))
+ return true;
+
+ if (!ifElseStatement.TrueStatement.AcceptVisitor(this))
+ return false;
+
+ //No need to worry about null ast nodes, since AcceptVisitor will just
+ //return false in those cases
+ return ifElseStatement.FalseStatement.AcceptVisitor(this);
+ }
+
+ public override bool VisitForeachStatement(ForeachStatement foreachStatement)
+ {
+ //Even if the body is always recursive, the function may stop if the collection
+ // is empty.
+ return foreachStatement.InExpression.AcceptVisitor(this);
+ }
+
+ public override bool VisitForStatement(ForStatement forStatement)
+ {
+ if (forStatement.Initializers.Any(initializer => initializer.AcceptVisitor(this)))
+ return true;
+
+ return forStatement.Condition.AcceptVisitor(this);
+ }
+
+ public override bool VisitSwitchStatement(SwitchStatement switchStatement)
+ {
+ if (switchStatement.Expression.AcceptVisitor(this)) {
+ return true;
+ }
+
+ bool foundDefault = false;
+ foreach (var section in switchStatement.SwitchSections) {
+ foundDefault = foundDefault || section.CaseLabels.Any(label => label.Expression.IsNull);
+ if (!section.AcceptVisitor(this))
+ return false;
+ }
+
+ return foundDefault;
+ }
+
+ public override bool VisitBlockStatement(BlockStatement blockStatement)
+ {
+ //If the block has a recursive statement, then that statement will be visited
+ //individually by the CFG construction algorithm later.
+ return false;
+ }
+
+ protected override bool VisitChildren(AstNode node)
+ {
+ return VisitNodeList(node.Children);
+ }
+
+ bool VisitNodeList(IEnumerable nodes) {
+ return nodes.Any(node => node.AcceptVisitor(this));
+ }
+
+ public override bool VisitQueryExpression(QueryExpression queryExpression)
+ {
+ //We only care about the first from clause because:
+ //in "from x in Method() select x", Method() might be recursive
+ //but in "from x in Bar() from y in Method() select x + y", even if Method() is recursive
+ //Bar might still be empty.
+ var queryFromClause = queryExpression.Clauses.OfType().FirstOrDefault();
+ if (queryFromClause == null)
+ return true;
+ return queryFromClause.AcceptVisitor(this);
+ }
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs
new file mode 100644
index 000000000..0528d4885
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs
@@ -0,0 +1,685 @@
+// Copyright (c) 2010-2013 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.Diagnostics;
+using System.Linq;
+using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.TypeSystem;
+using System.Threading;
+using ICSharpCode.NRefactory.CSharp.Completion;
+using System.Collections.ObjectModel;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ ///
+ /// C# Semantic highlighter.
+ ///
+ public abstract class SemanticHighlightingVisitor : DepthFirstAstVisitor
+ {
+ protected CancellationToken cancellationToken = default (CancellationToken);
+
+ protected TColor defaultTextColor;
+ protected TColor referenceTypeColor;
+ protected TColor valueTypeColor;
+ protected TColor interfaceTypeColor;
+ protected TColor enumerationTypeColor;
+ protected TColor typeParameterTypeColor;
+ protected TColor delegateTypeColor;
+
+ protected TColor methodCallColor;
+ protected TColor methodDeclarationColor;
+
+ protected TColor eventDeclarationColor;
+ protected TColor eventAccessColor;
+
+ protected TColor propertyDeclarationColor;
+ protected TColor propertyAccessColor;
+
+ protected TColor fieldDeclarationColor;
+ protected TColor fieldAccessColor;
+
+ protected TColor variableDeclarationColor;
+ protected TColor variableAccessColor;
+
+ protected TColor parameterDeclarationColor;
+ protected TColor parameterAccessColor;
+
+ protected TColor valueKeywordColor;
+ protected TColor externAliasKeywordColor;
+ protected TColor varKeywordTypeColor;
+
+ ///
+ /// Used for 'in' modifiers on type parameters.
+ ///
+ ///
+ /// 'in' may have a different color when used with 'foreach'.
+ /// 'out' is not colored by semantic highlighting, as syntax highlighting can already detect it as a parameter modifier.
+ ///
+ protected TColor parameterModifierColor;
+
+ ///
+ /// Used for inactive code (excluded by preprocessor or ConditionalAttribute)
+ ///
+ protected TColor inactiveCodeColor;
+
+ protected TColor stringFormatItemColor;
+
+
+ protected TColor syntaxErrorColor;
+
+ protected TextLocation regionStart;
+ protected TextLocation regionEnd;
+
+ protected CSharpAstResolver resolver;
+ protected bool isInAccessorContainingValueParameter;
+
+ protected abstract void Colorize(TextLocation start, TextLocation end, TColor color);
+
+ #region Colorize helper methods
+ protected void Colorize(Identifier identifier, ResolveResult rr)
+ {
+ if (identifier.IsNull)
+ return;
+ if (rr.IsError) {
+ Colorize(identifier, syntaxErrorColor);
+ return;
+ }
+ if (rr is TypeResolveResult) {
+ if (blockDepth > 0 && identifier.Name == "var" && rr.Type.Kind != TypeKind.Null && rr.Type.Name != "var" ) {
+ Colorize(identifier, varKeywordTypeColor);
+ return;
+ }
+
+ TColor color;
+ if (TryGetTypeHighlighting (rr.Type.Kind, out color)) {
+ Colorize(identifier, color);
+ }
+ return;
+ }
+ var mrr = rr as MemberResolveResult;
+ if (mrr != null) {
+ TColor color;
+ if (TryGetMemberColor (mrr.Member, out color)) {
+ Colorize(identifier, color);
+ return;
+ }
+ }
+
+ if (rr is MethodGroupResolveResult) {
+ Colorize (identifier, methodCallColor);
+ return;
+ }
+
+ var localResult = rr as LocalResolveResult;
+ if (localResult != null) {
+ if (localResult.Variable is IParameter) {
+ Colorize (identifier, parameterAccessColor);
+ } else {
+ Colorize (identifier, variableAccessColor);
+ }
+ }
+
+
+ VisitIdentifier(identifier); // un-colorize contextual keywords
+ }
+
+ protected void Colorize(AstNode node, TColor color)
+ {
+ if (node.IsNull)
+ return;
+ Colorize(node.StartLocation, node.EndLocation, color);
+ }
+ #endregion
+
+ protected override void VisitChildren(AstNode node)
+ {
+ for (var child = node.FirstChild; child != null; child = child.NextSibling) {
+ if (child.StartLocation < regionEnd && child.EndLocation > regionStart)
+ child.AcceptVisitor(this);
+ }
+ }
+
+ ///
+ /// Visit all children of node until (but excluding) end.
+ /// If end is a null node, nothing will be visited.
+ ///
+ protected void VisitChildrenUntil(AstNode node, AstNode end)
+ {
+ if (end.IsNull)
+ return;
+ Debug.Assert(node == end.Parent);
+ for (var child = node.FirstChild; child != end; child = child.NextSibling) {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (child.StartLocation < regionEnd && child.EndLocation > regionStart)
+ child.AcceptVisitor(this);
+ }
+ }
+
+ ///
+ /// Visit all children of node after (excluding) start.
+ /// If start is a null node, all children will be visited.
+ ///
+ protected void VisitChildrenAfter(AstNode node, AstNode start)
+ {
+ Debug.Assert(start.IsNull || start.Parent == node);
+ for (var child = (start.IsNull ? node.FirstChild : start.NextSibling); child != null; child = child.NextSibling) {
+ cancellationToken.ThrowIfCancellationRequested();
+ if (child.StartLocation < regionEnd && child.EndLocation > regionStart)
+ child.AcceptVisitor(this);
+ }
+ }
+
+ public override void VisitIdentifier(Identifier identifier)
+ {
+ switch (identifier.Name) {
+ case "add":
+ case "async":
+ case "await":
+ case "get":
+ case "partial":
+ case "remove":
+ case "set":
+ case "where":
+ case "yield":
+ case "from":
+ case "select":
+ case "group":
+ case "into":
+ case "orderby":
+ case "join":
+ case "let":
+ case "on":
+ case "equals":
+ case "by":
+ case "ascending":
+ case "descending":
+ case "dynamic":
+ case "var":
+ // Reset color of contextual keyword to default if it's used as an identifier.
+ // Note that this method does not get called when 'var' or 'dynamic' is used as a type,
+ // because types get highlighted with valueTypeColor/referenceTypeColor instead.
+ Colorize(identifier, defaultTextColor);
+ break;
+ case "global":
+ // Reset color of 'global' keyword to default unless its used as part of 'global::'.
+ MemberType parentMemberType = identifier.Parent as MemberType;
+ if (parentMemberType == null || !parentMemberType.IsDoubleColon)
+ Colorize(identifier, defaultTextColor);
+ break;
+ }
+ // "value" is handled in VisitIdentifierExpression()
+ // "alias" is handled in VisitExternAliasDeclaration()
+ }
+
+ public override void VisitSimpleType(SimpleType simpleType)
+ {
+ var identifierToken = simpleType.IdentifierToken;
+ VisitChildrenUntil(simpleType, identifierToken);
+ Colorize(identifierToken, resolver.Resolve(simpleType, cancellationToken));
+ VisitChildrenAfter(simpleType, identifierToken);
+ }
+
+ public override void VisitMemberType(MemberType memberType)
+ {
+ var memberNameToken = memberType.MemberNameToken;
+ VisitChildrenUntil(memberType, memberNameToken);
+ Colorize(memberNameToken, resolver.Resolve(memberType, cancellationToken));
+ VisitChildrenAfter(memberType, memberNameToken);
+ }
+
+ public override void VisitIdentifierExpression(IdentifierExpression identifierExpression)
+ {
+ var identifier = identifierExpression.IdentifierToken;
+ VisitChildrenUntil(identifierExpression, identifier);
+ if (isInAccessorContainingValueParameter && identifierExpression.Identifier == "value") {
+ Colorize(identifier, valueKeywordColor);
+ } else {
+ Colorize(identifier, resolver.Resolve(identifierExpression, cancellationToken));
+ }
+ VisitChildrenAfter(identifierExpression, identifier);
+ }
+
+ public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
+ {
+ var memberNameToken = memberReferenceExpression.MemberNameToken;
+ VisitChildrenUntil(memberReferenceExpression, memberNameToken);
+ ResolveResult rr = resolver.Resolve(memberReferenceExpression, cancellationToken);
+ Colorize(memberNameToken, rr);
+ VisitChildrenAfter(memberReferenceExpression, memberNameToken);
+ }
+
+ void HighlightStringFormatItems(PrimitiveExpression expr)
+ {
+ if (!(expr.Value is string))
+ return;
+ int line = expr.StartLocation.Line;
+ int col = expr.StartLocation.Column;
+ TextLocation start = TextLocation.Empty;
+ for (int i = 0; i < expr.LiteralValue.Length; i++) {
+ char ch = expr.LiteralValue [i];
+
+ if (NewLine.GetDelimiterType(ch, i + 1 < expr.LiteralValue.Length ? expr.LiteralValue [i + 1] : '\0') != UnicodeNewline.Unknown) {
+ line++;
+ col = 1;
+ continue;
+ }
+
+
+ if (ch == '{' && start.IsEmpty) {
+ char next = i + 1 < expr.LiteralValue.Length ? expr.LiteralValue [i + 1] : '\0';
+ if (next == '{') {
+ i++;
+ col += 2;
+ continue;
+ }
+ start = new TextLocation(line, col);
+ }
+ if (ch == '}' &&!start.IsEmpty) {
+ Colorize(start, new TextLocation(line, col + 1), stringFormatItemColor);
+ start = TextLocation.Empty;
+ }
+ col++;
+ }
+
+ }
+
+ public override void VisitInvocationExpression(InvocationExpression invocationExpression)
+ {
+ Expression target = invocationExpression.Target;
+ if (target is IdentifierExpression || target is MemberReferenceExpression || target is PointerReferenceExpression) {
+ var invocationRR = resolver.Resolve(invocationExpression, cancellationToken) as CSharpInvocationResolveResult;
+ if (invocationRR != null) {
+ if (invocationExpression.Parent is ExpressionStatement && (IsInactiveConditionalMethod(invocationRR.Member) || IsEmptyPartialMethod(invocationRR.Member))) {
+ // mark the whole invocation statement as inactive code
+ Colorize(invocationExpression.Parent, inactiveCodeColor);
+ return;
+ }
+
+ Expression fmtArgumets;
+ IList args;
+ if (invocationRR.Arguments.Count > 1 && FormatStringHelper.TryGetFormattingParameters(invocationRR, invocationExpression, out fmtArgumets, out args, null)) {
+ var expr = invocationExpression.Arguments.First() as PrimitiveExpression;
+ if (expr != null)
+ HighlightStringFormatItems(expr);
+ }
+ }
+
+ VisitChildrenUntil(invocationExpression, target);
+
+ // highlight the method call
+ var identifier = target.GetChildByRole(Roles.Identifier);
+ VisitChildrenUntil(target, identifier);
+ if (invocationRR != null && !invocationRR.IsDelegateInvocation) {
+ Colorize(identifier, methodCallColor);
+ } else {
+ ResolveResult targetRR = resolver.Resolve(target, cancellationToken);
+ Colorize(identifier, targetRR);
+ }
+ VisitChildrenAfter(target, identifier);
+ VisitChildrenAfter(invocationExpression, target);
+ } else {
+ VisitChildren(invocationExpression);
+ }
+ }
+
+ #region IsInactiveConditional helper methods
+ bool IsInactiveConditionalMethod(IParameterizedMember member)
+ {
+ if (member.SymbolKind != SymbolKind.Method || member.ReturnType.Kind != TypeKind.Void)
+ return false;
+ foreach (var baseMember in InheritanceHelper.GetBaseMembers(member, false)) {
+ if (IsInactiveConditional (baseMember.Attributes))
+ return true;
+ }
+ return IsInactiveConditional(member.Attributes);
+ }
+
+ static bool IsEmptyPartialMethod(IParameterizedMember member)
+ {
+ if (member.SymbolKind != SymbolKind.Method || member.ReturnType.Kind != TypeKind.Void)
+ return false;
+ var method = (IMethod)member;
+ return method.IsPartial && !method.HasBody;
+ }
+
+ bool IsInactiveConditional(IList attributes)
+ {
+ bool hasConditionalAttribute = false;
+ foreach (var attr in attributes) {
+ if (attr.AttributeType.Name == "ConditionalAttribute" && attr.AttributeType.Namespace == "System.Diagnostics" && attr.PositionalArguments.Count == 1) {
+ string symbol = attr.PositionalArguments[0].ConstantValue as string;
+ if (symbol != null) {
+ hasConditionalAttribute = true;
+ var cu = this.resolver.RootNode as SyntaxTree;
+ if (cu != null) {
+ if (cu.ConditionalSymbols.Contains(symbol))
+ return false; // conditional is active
+ }
+ }
+ }
+ }
+
+ return hasConditionalAttribute;
+ }
+ #endregion
+
+ public override void VisitExternAliasDeclaration (ExternAliasDeclaration externAliasDeclaration)
+ {
+ var aliasToken = externAliasDeclaration.AliasToken;
+ VisitChildrenUntil(externAliasDeclaration, aliasToken);
+ Colorize (aliasToken, externAliasKeywordColor);
+ VisitChildrenAfter(externAliasDeclaration, aliasToken);
+ }
+
+ public override void VisitAccessor(Accessor accessor)
+ {
+ isInAccessorContainingValueParameter = accessor.Role != PropertyDeclaration.GetterRole;
+ try {
+ VisitChildren(accessor);
+ } finally {
+ isInAccessorContainingValueParameter = false;
+ }
+ }
+
+ bool CheckInterfaceImplementation (EntityDeclaration entityDeclaration)
+ {
+ var result = resolver.Resolve (entityDeclaration, cancellationToken) as MemberResolveResult;
+ if (result == null)
+ return false;
+ if (result.Member.ImplementedInterfaceMembers.Count == 0) {
+ Colorize (entityDeclaration.NameToken, syntaxErrorColor);
+ return false;
+ }
+ return true;
+ }
+
+ public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration)
+ {
+ var nameToken = methodDeclaration.NameToken;
+ VisitChildrenUntil(methodDeclaration, nameToken);
+ if (!methodDeclaration.PrivateImplementationType.IsNull) {
+ if (!CheckInterfaceImplementation (methodDeclaration)) {
+ VisitChildrenAfter(methodDeclaration, nameToken);
+ return;
+ }
+ }
+ Colorize(nameToken, methodDeclarationColor);
+ VisitChildrenAfter(methodDeclaration, nameToken);
+ }
+
+ public override void VisitParameterDeclaration(ParameterDeclaration parameterDeclaration)
+ {
+ var nameToken = parameterDeclaration.NameToken;
+ VisitChildrenUntil(parameterDeclaration, nameToken);
+ Colorize(nameToken, parameterDeclarationColor);
+ VisitChildrenAfter(parameterDeclaration, nameToken);
+ }
+
+ public override void VisitEventDeclaration(EventDeclaration eventDeclaration)
+ {
+ var nameToken = eventDeclaration.NameToken;
+ VisitChildrenUntil(eventDeclaration, nameToken);
+ Colorize(nameToken, eventDeclarationColor);
+ VisitChildrenAfter(eventDeclaration, nameToken);
+ }
+
+ public override void VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration)
+ {
+ var nameToken = eventDeclaration.NameToken;
+ VisitChildrenUntil(eventDeclaration, nameToken);
+ if (!eventDeclaration.PrivateImplementationType.IsNull) {
+ if (!CheckInterfaceImplementation (eventDeclaration)) {
+ VisitChildrenAfter(eventDeclaration, nameToken);
+ return;
+ }
+ }
+ Colorize(nameToken, eventDeclarationColor);
+ VisitChildrenAfter(eventDeclaration, nameToken);
+ }
+
+ public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration)
+ {
+ var nameToken = propertyDeclaration.NameToken;
+ VisitChildrenUntil(propertyDeclaration, nameToken);
+ if (!propertyDeclaration.PrivateImplementationType.IsNull) {
+ if (!CheckInterfaceImplementation (propertyDeclaration)) {
+ VisitChildrenAfter(propertyDeclaration, nameToken);
+ return;
+ }
+ }
+ Colorize(nameToken, propertyDeclarationColor);
+ VisitChildrenAfter(propertyDeclaration, nameToken);
+ }
+
+ public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration)
+ {
+ base.VisitIndexerDeclaration(indexerDeclaration);
+ if (!indexerDeclaration.PrivateImplementationType.IsNull) {
+ CheckInterfaceImplementation (indexerDeclaration);
+ }
+ }
+
+ public override void VisitFieldDeclaration(FieldDeclaration fieldDeclaration)
+ {
+ fieldDeclaration.ReturnType.AcceptVisitor (this);
+ foreach (var init in fieldDeclaration.Variables) {
+ Colorize (init.NameToken, fieldDeclarationColor);
+ init.Initializer.AcceptVisitor (this);
+ }
+ }
+
+ public override void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration)
+ {
+ fixedFieldDeclaration.ReturnType.AcceptVisitor (this);
+ foreach (var init in fixedFieldDeclaration.Variables) {
+ Colorize (init.NameToken, fieldDeclarationColor);
+ init.CountExpression.AcceptVisitor (this);
+ }
+ }
+
+ public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration)
+ {
+ HandleConstructorOrDestructor(constructorDeclaration);
+ }
+
+ public override void VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration)
+ {
+ HandleConstructorOrDestructor(destructorDeclaration);
+ }
+
+ void HandleConstructorOrDestructor(AstNode constructorDeclaration)
+ {
+ Identifier nameToken = constructorDeclaration.GetChildByRole(Roles.Identifier);
+ VisitChildrenUntil(constructorDeclaration, nameToken);
+ var currentTypeDef = resolver.GetResolverStateBefore(constructorDeclaration).CurrentTypeDefinition;
+ if (currentTypeDef != null && nameToken.Name == currentTypeDef.Name) {
+ TColor color;
+ if (TryGetTypeHighlighting (currentTypeDef.Kind, out color))
+ Colorize(nameToken, color);
+ }
+ VisitChildrenAfter(constructorDeclaration, nameToken);
+ }
+
+ bool TryGetMemberColor(IMember member, out TColor color)
+ {
+ switch (member.SymbolKind) {
+ case SymbolKind.Field:
+ color = fieldAccessColor;
+ return true;
+ case SymbolKind.Property:
+ color = propertyAccessColor;
+ return true;
+ case SymbolKind.Event:
+ color = eventAccessColor;
+ return true;
+ case SymbolKind.Method:
+ color = methodCallColor;
+ return true;
+ case SymbolKind.Constructor:
+ case SymbolKind.Destructor:
+ return TryGetTypeHighlighting (member.DeclaringType.Kind, out color);
+ default:
+ color = default (TColor);
+ return false;
+ }
+ }
+
+ TColor GetTypeHighlighting (ClassType classType)
+ {
+ switch (classType) {
+ case ClassType.Class:
+ return referenceTypeColor;
+ case ClassType.Struct:
+ return valueTypeColor;
+ case ClassType.Interface:
+ return interfaceTypeColor;
+ case ClassType.Enum:
+ return enumerationTypeColor;
+ default:
+ throw new InvalidOperationException ("Unknown class type :" + classType);
+ }
+ }
+
+ bool TryGetTypeHighlighting (TypeKind kind, out TColor color)
+ {
+ switch (kind) {
+ case TypeKind.Class:
+ color = referenceTypeColor;
+ return true;
+ case TypeKind.Struct:
+ color = valueTypeColor;
+ return true;
+ case TypeKind.Interface:
+ color = interfaceTypeColor;
+ return true;
+ case TypeKind.Enum:
+ color = enumerationTypeColor;
+ return true;
+ case TypeKind.TypeParameter:
+ color = typeParameterTypeColor;
+ return true;
+ case TypeKind.Delegate:
+ color = delegateTypeColor;
+ return true;
+ case TypeKind.Unknown:
+ case TypeKind.Null:
+ color = syntaxErrorColor;
+ return true;
+ default:
+ color = default (TColor);
+ return false;
+ }
+ }
+
+ public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
+ {
+ var nameToken = typeDeclaration.NameToken;
+ VisitChildrenUntil(typeDeclaration, nameToken);
+ Colorize(nameToken, GetTypeHighlighting (typeDeclaration.ClassType));
+ VisitChildrenAfter(typeDeclaration, nameToken);
+ }
+
+ public override void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration)
+ {
+ if (typeParameterDeclaration.Variance == VarianceModifier.Contravariant)
+ Colorize(typeParameterDeclaration.VarianceToken, parameterModifierColor);
+
+ // bool isValueType = false;
+ // if (typeParameterDeclaration.Parent != null) {
+ // foreach (var constraint in typeParameterDeclaration.Parent.GetChildrenByRole(Roles.Constraint)) {
+ // if (constraint.TypeParameter.Identifier == typeParameterDeclaration.Name) {
+ // isValueType = constraint.BaseTypes.OfType().Any(p => p.Keyword == "struct");
+ // }
+ // }
+ // }
+ var nameToken = typeParameterDeclaration.NameToken;
+ VisitChildrenUntil(typeParameterDeclaration, nameToken);
+ Colorize(nameToken, typeParameterTypeColor); /*isValueType ? valueTypeColor : referenceTypeColor*/
+ VisitChildrenAfter(typeParameterDeclaration, nameToken);
+ }
+
+ public override void VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration)
+ {
+ var nameToken = delegateDeclaration.NameToken;
+ VisitChildrenUntil(delegateDeclaration, nameToken);
+ Colorize(nameToken, delegateTypeColor);
+ VisitChildrenAfter(delegateDeclaration, nameToken);
+ }
+
+ public override void VisitVariableInitializer(VariableInitializer variableInitializer)
+ {
+ var nameToken = variableInitializer.NameToken;
+ VisitChildrenUntil(variableInitializer, nameToken);
+ if (variableInitializer.Parent is FieldDeclaration) {
+ Colorize(nameToken, fieldDeclarationColor);
+ } else if (variableInitializer.Parent is EventDeclaration) {
+ Colorize(nameToken, eventDeclarationColor);
+ } else {
+ Colorize(nameToken, variableDeclarationColor);
+ }
+ VisitChildrenAfter(variableInitializer, nameToken);
+ }
+
+ public override void VisitComment(Comment comment)
+ {
+ if (comment.CommentType == CommentType.InactiveCode) {
+ Colorize(comment, inactiveCodeColor);
+ }
+ }
+
+ public override void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective)
+ {
+ }
+
+ public override void VisitAttribute(ICSharpCode.NRefactory.CSharp.Attribute attribute)
+ {
+ ITypeDefinition attrDef = resolver.Resolve(attribute.Type, cancellationToken).Type.GetDefinition();
+ if (attrDef != null && IsInactiveConditional(attrDef.Attributes)) {
+ Colorize(attribute, inactiveCodeColor);
+ } else {
+ VisitChildren(attribute);
+ }
+ }
+
+ public override void VisitArrayInitializerExpression (ArrayInitializerExpression arrayInitializerExpression)
+ {
+ foreach (var a in arrayInitializerExpression.Elements) {
+ var namedElement = a as NamedExpression;
+ if (namedElement != null) {
+ var result = resolver.Resolve (namedElement, cancellationToken);
+ if (result.IsError)
+ Colorize (namedElement.NameToken, syntaxErrorColor);
+ namedElement.Expression.AcceptVisitor (this);
+ } else {
+ a.AcceptVisitor (this);
+ }
+ }
+ }
+
+ int blockDepth;
+ public override void VisitBlockStatement(BlockStatement blockStatement)
+ {
+ blockDepth++;
+ base.VisitBlockStatement(blockStatement);
+ blockDepth--;
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs
new file mode 100644
index 000000000..8676beb67
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNode.cs
@@ -0,0 +1,1042 @@
+//
+// AstNode.cs
+//
+// Author:
+// Mike Krüger
+//
+// Copyright (c) 2009 Novell, Inc (http://www.novell.com)
+//
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ public abstract class AstNode : AbstractAnnotatable, ICSharpCode.NRefactory.TypeSystem.IFreezable, PatternMatching.INode, ICloneable
+ {
+ // the Root role must be available when creating the null nodes, so we can't put it in the Roles class
+ internal static readonly Role RootRole = new Role ("Root");
+
+ #region Null
+ public static readonly AstNode Null = new NullAstNode ();
+
+ sealed class NullAstNode : AstNode
+ {
+ public override NodeType NodeType {
+ get {
+ return NodeType.Unknown;
+ }
+ }
+
+ public override bool IsNull {
+ get {
+ return true;
+ }
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitNullNode(this);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitNullNode(this);
+ }
+
+ public override S AcceptVisitor (IAstVisitor visitor, T data)
+ {
+ return visitor.VisitNullNode(this, data);
+ }
+
+ protected internal override bool DoMatch (AstNode other, PatternMatching.Match match)
+ {
+ return other == null || other.IsNull;
+ }
+ }
+ #endregion
+
+ #region PatternPlaceholder
+ public static implicit operator AstNode (PatternMatching.Pattern pattern)
+ {
+ return pattern != null ? new PatternPlaceholder (pattern) : null;
+ }
+
+ sealed class PatternPlaceholder : AstNode, PatternMatching.INode
+ {
+ readonly PatternMatching.Pattern child;
+
+ public PatternPlaceholder (PatternMatching.Pattern child)
+ {
+ this.child = child;
+ }
+
+ public override NodeType NodeType {
+ get { return NodeType.Pattern; }
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitPatternPlaceholder (this, child);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitPatternPlaceholder (this, child);
+ }
+
+ public override S AcceptVisitor (IAstVisitor visitor, T data)
+ {
+ return visitor.VisitPatternPlaceholder (this, child, data);
+ }
+
+ protected internal override bool DoMatch (AstNode other, PatternMatching.Match match)
+ {
+ return child.DoMatch (other, match);
+ }
+
+ bool PatternMatching.INode.DoMatchCollection (Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo)
+ {
+ return child.DoMatchCollection (role, pos, match, backtrackingInfo);
+ }
+ }
+ #endregion
+
+ AstNode parent;
+ AstNode prevSibling;
+ AstNode nextSibling;
+ AstNode firstChild;
+ AstNode lastChild;
+
+ // Flags, from least significant to most significant bits:
+ // - Role.RoleIndexBits: role index
+ // - 1 bit: IsFrozen
+ protected uint flags = RootRole.Index;
+ // Derived classes may also use a few bits,
+ // for example Identifier uses 1 bit for IsVerbatim
+
+ const uint roleIndexMask = (1u << Role.RoleIndexBits) - 1;
+ const uint frozenBit = 1u << Role.RoleIndexBits;
+ protected const int AstNodeFlagsUsedBits = Role.RoleIndexBits + 1;
+
+ protected AstNode()
+ {
+ if (IsNull)
+ Freeze();
+ }
+
+ public bool IsFrozen {
+ get { return (flags & frozenBit) != 0; }
+ }
+
+ public void Freeze()
+ {
+ if (!IsFrozen) {
+ for (AstNode child = firstChild; child != null; child = child.nextSibling)
+ child.Freeze();
+ flags |= frozenBit;
+ }
+ }
+
+ protected void ThrowIfFrozen()
+ {
+ if (IsFrozen)
+ throw new InvalidOperationException("Cannot mutate frozen " + GetType().Name);
+ }
+
+ public abstract NodeType NodeType {
+ get;
+ }
+
+ public virtual bool IsNull {
+ get {
+ return false;
+ }
+ }
+
+ public virtual TextLocation StartLocation {
+ get {
+ var child = firstChild;
+ if (child == null)
+ return TextLocation.Empty;
+ return child.StartLocation;
+ }
+ }
+
+ public virtual TextLocation EndLocation {
+ get {
+ var child = lastChild;
+ if (child == null)
+ return TextLocation.Empty;
+ return child.EndLocation;
+ }
+ }
+
+ public DomRegion Region {
+ get {
+ return new DomRegion (StartLocation, EndLocation);
+ }
+ }
+
+ ///
+ /// Gets the region from StartLocation to EndLocation for this node.
+ /// The file name of the region is set based on the parent SyntaxTree's file name.
+ /// If this node is not connected to a whole compilation, the file name will be null.
+ ///
+ public ICSharpCode.NRefactory.TypeSystem.DomRegion GetRegion()
+ {
+ var syntaxTree = (this.Ancestors.LastOrDefault() ?? this) as SyntaxTree;
+ string fileName = (syntaxTree != null ? syntaxTree.FileName : null);
+ return new ICSharpCode.NRefactory.TypeSystem.DomRegion(fileName, this.StartLocation, this.EndLocation);
+ }
+
+ public AstNode Parent {
+ get { return parent; }
+ }
+
+ public Role Role {
+ get {
+ return Role.GetByIndex(flags & roleIndexMask);
+ }
+ set {
+ if (value == null)
+ throw new ArgumentNullException("value");
+ if (!value.IsValid(this))
+ throw new ArgumentException("This node is not valid in the new role.");
+ ThrowIfFrozen();
+ SetRole(value);
+ }
+ }
+
+ internal uint RoleIndex {
+ get { return flags & roleIndexMask; }
+ }
+
+ void SetRole(Role role)
+ {
+ flags = (flags & ~roleIndexMask) | role.Index;
+ }
+
+ public AstNode NextSibling {
+ get { return nextSibling; }
+ }
+
+ public AstNode PrevSibling {
+ get { return prevSibling; }
+ }
+
+ public AstNode FirstChild {
+ get { return firstChild; }
+ }
+
+ public AstNode LastChild {
+ get { return lastChild; }
+ }
+
+ public bool HasChildren {
+ get {
+ return firstChild != null;
+ }
+ }
+
+ public IEnumerable Children {
+ get {
+ AstNode next;
+ for (AstNode cur = firstChild; cur != null; cur = next) {
+ Debug.Assert (cur.parent == this);
+ // Remember next before yielding cur.
+ // This allows removing/replacing nodes while iterating through the list.
+ next = cur.nextSibling;
+ yield return cur;
+ }
+ }
+ }
+
+ ///
+ /// Gets the ancestors of this node (excluding this node itself)
+ ///
+ public IEnumerable Ancestors {
+ get {
+ for (AstNode cur = parent; cur != null; cur = cur.parent) {
+ yield return cur;
+ }
+ }
+ }
+
+ ///
+ /// Gets the ancestors of this node (including this node itself)
+ ///
+ public IEnumerable AncestorsAndSelf {
+ get {
+ for (AstNode cur = this; cur != null; cur = cur.parent) {
+ yield return cur;
+ }
+ }
+ }
+
+ ///
+ /// Gets all descendants of this node (excluding this node itself) in pre-order.
+ ///
+ public IEnumerable Descendants {
+ get { return GetDescendantsImpl(false); }
+ }
+
+ ///
+ /// Gets all descendants of this node (including this node itself) in pre-order.
+ ///
+ public IEnumerable DescendantsAndSelf {
+ get { return GetDescendantsImpl(true); }
+ }
+
+ static bool IsInsideRegion(DomRegion region, AstNode pos)
+ {
+ if (region.IsEmpty)
+ return true;
+ var nodeRegion = pos.Region;
+ return region.IntersectsWith(nodeRegion) || region.OverlapsWith(nodeRegion);
+ }
+
+ public IEnumerable DescendantNodes (Func descendIntoChildren = null)
+ {
+ return GetDescendantsImpl(false, new DomRegion (), descendIntoChildren);
+ }
+
+ public IEnumerable DescendantNodes (DomRegion region, Func descendIntoChildren = null)
+ {
+ return GetDescendantsImpl(false, region, descendIntoChildren);
+ }
+
+ public IEnumerable DescendantNodesAndSelf (Func descendIntoChildren = null)
+ {
+ return GetDescendantsImpl(true, new DomRegion (), descendIntoChildren);
+ }
+
+ public IEnumerable DescendantNodesAndSelf (DomRegion region, Func descendIntoChildren = null)
+ {
+ return GetDescendantsImpl(true, region, descendIntoChildren);
+ }
+
+ IEnumerable GetDescendantsImpl(bool includeSelf, DomRegion region = new DomRegion (), Func descendIntoChildren = null)
+ {
+ if (includeSelf) {
+ if (IsInsideRegion (region, this))
+ yield return this;
+ if (descendIntoChildren != null && !descendIntoChildren(this))
+ yield break;
+ }
+
+ Stack nextStack = new Stack();
+ nextStack.Push(null);
+ AstNode pos = firstChild;
+ while (pos != null) {
+ // Remember next before yielding pos.
+ // This allows removing/replacing nodes while iterating through the list.
+ if (pos.nextSibling != null)
+ nextStack.Push(pos.nextSibling);
+ if (IsInsideRegion(region, pos))
+ yield return pos;
+ if (pos.firstChild != null && (descendIntoChildren == null || descendIntoChildren(pos)))
+ pos = pos.firstChild;
+ else
+ pos = nextStack.Pop();
+ }
+ }
+
+ ///
+ /// Gets the first child with the specified role.
+ /// Returns the role's null object if the child is not found.
+ ///
+ public T GetChildByRole(Role role) where T : AstNode
+ {
+ if (role == null)
+ throw new ArgumentNullException ("role");
+ uint roleIndex = role.Index;
+ for (var cur = firstChild; cur != null; cur = cur.nextSibling) {
+ if ((cur.flags & roleIndexMask) == roleIndex)
+ return (T)cur;
+ }
+ return role.NullObject;
+ }
+
+ public T GetParent() where T : AstNode
+ {
+ return Ancestors.OfType().FirstOrDefault();
+ }
+
+ public AstNode GetParent(Func pred)
+ {
+ return Ancestors.FirstOrDefault(pred);
+ }
+
+ public AstNodeCollection GetChildrenByRole (Role role) where T : AstNode
+ {
+ return new AstNodeCollection (this, role);
+ }
+
+ protected void SetChildByRole (Role role, T newChild) where T : AstNode
+ {
+ AstNode oldChild = GetChildByRole (role);
+ if (oldChild.IsNull)
+ AddChild (newChild, role);
+ else
+ oldChild.ReplaceWith (newChild);
+ }
+
+ public void AddChild (T child, Role role) where T : AstNode
+ {
+ if (role == null)
+ throw new ArgumentNullException ("role");
+ if (child == null || child.IsNull)
+ return;
+ ThrowIfFrozen();
+ if (child == this)
+ throw new ArgumentException ("Cannot add a node to itself as a child.", "child");
+ if (child.parent != null)
+ throw new ArgumentException ("Node is already used in another tree.", "child");
+ if (child.IsFrozen)
+ throw new ArgumentException ("Cannot add a frozen node.", "child");
+ AddChildUnsafe (child, role);
+ }
+
+ public void AddChildWithExistingRole (AstNode child)
+ {
+ if (child == null || child.IsNull)
+ return;
+ ThrowIfFrozen();
+ if (child == this)
+ throw new ArgumentException ("Cannot add a node to itself as a child.", "child");
+ if (child.parent != null)
+ throw new ArgumentException ("Node is already used in another tree.", "child");
+ if (child.IsFrozen)
+ throw new ArgumentException ("Cannot add a frozen node.", "child");
+ AddChildUnsafe (child, child.Role);
+ }
+
+ ///
+ /// Adds a child without performing any safety checks.
+ ///
+ internal void AddChildUnsafe (AstNode child, Role role)
+ {
+ child.parent = this;
+ child.SetRole(role);
+ if (firstChild == null) {
+ lastChild = firstChild = child;
+ } else {
+ lastChild.nextSibling = child;
+ child.prevSibling = lastChild;
+ lastChild = child;
+ }
+ }
+
+ public void InsertChildBefore (AstNode nextSibling, T child, Role role) where T : AstNode
+ {
+ if (role == null)
+ throw new ArgumentNullException ("role");
+ if (nextSibling == null || nextSibling.IsNull) {
+ AddChild (child, role);
+ return;
+ }
+
+ if (child == null || child.IsNull)
+ return;
+ ThrowIfFrozen();
+ if (child.parent != null)
+ throw new ArgumentException ("Node is already used in another tree.", "child");
+ if (child.IsFrozen)
+ throw new ArgumentException ("Cannot add a frozen node.", "child");
+ if (nextSibling.parent != this)
+ throw new ArgumentException ("NextSibling is not a child of this node.", "nextSibling");
+ // No need to test for "Cannot add children to null nodes",
+ // as there isn't any valid nextSibling in null nodes.
+ InsertChildBeforeUnsafe (nextSibling, child, role);
+ }
+
+ internal void InsertChildBeforeUnsafe (AstNode nextSibling, AstNode child, Role role)
+ {
+ child.parent = this;
+ child.SetRole(role);
+ child.nextSibling = nextSibling;
+ child.prevSibling = nextSibling.prevSibling;
+
+ if (nextSibling.prevSibling != null) {
+ Debug.Assert (nextSibling.prevSibling.nextSibling == nextSibling);
+ nextSibling.prevSibling.nextSibling = child;
+ } else {
+ Debug.Assert (firstChild == nextSibling);
+ firstChild = child;
+ }
+ nextSibling.prevSibling = child;
+ }
+
+ public void InsertChildAfter (AstNode prevSibling, T child, Role role) where T : AstNode
+ {
+ InsertChildBefore ((prevSibling == null || prevSibling.IsNull) ? firstChild : prevSibling.nextSibling, child, role);
+ }
+
+ ///
+ /// Removes this node from its parent.
+ ///
+ public void Remove ()
+ {
+ if (parent != null) {
+ ThrowIfFrozen();
+ if (prevSibling != null) {
+ Debug.Assert (prevSibling.nextSibling == this);
+ prevSibling.nextSibling = nextSibling;
+ } else {
+ Debug.Assert (parent.firstChild == this);
+ parent.firstChild = nextSibling;
+ }
+ if (nextSibling != null) {
+ Debug.Assert (nextSibling.prevSibling == this);
+ nextSibling.prevSibling = prevSibling;
+ } else {
+ Debug.Assert (parent.lastChild == this);
+ parent.lastChild = prevSibling;
+ }
+ parent = null;
+ prevSibling = null;
+ nextSibling = null;
+ }
+ }
+
+ ///
+ /// Replaces this node with the new node.
+ ///
+ public void ReplaceWith (AstNode newNode)
+ {
+ if (newNode == null || newNode.IsNull) {
+ Remove ();
+ return;
+ }
+ if (newNode == this)
+ return; // nothing to do...
+ if (parent == null) {
+ throw new InvalidOperationException (this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node");
+ }
+ ThrowIfFrozen();
+ // Because this method doesn't statically check the new node's type with the role,
+ // we perform a runtime test:
+ if (!this.Role.IsValid (newNode)) {
+ throw new ArgumentException (string.Format ("The new node '{0}' is not valid in the role {1}", newNode.GetType ().Name, this.Role.ToString ()), "newNode");
+ }
+ if (newNode.parent != null) {
+ // newNode is used within this tree?
+ if (newNode.Ancestors.Contains (this)) {
+ // e.g. "parenthesizedExpr.ReplaceWith(parenthesizedExpr.Expression);"
+ // enable automatic removal
+ newNode.Remove ();
+ } else {
+ throw new ArgumentException ("Node is already used in another tree.", "newNode");
+ }
+ }
+ if (newNode.IsFrozen)
+ throw new ArgumentException ("Cannot add a frozen node.", "newNode");
+
+ newNode.parent = parent;
+ newNode.SetRole(this.Role);
+ newNode.prevSibling = prevSibling;
+ newNode.nextSibling = nextSibling;
+
+ if (prevSibling != null) {
+ Debug.Assert (prevSibling.nextSibling == this);
+ prevSibling.nextSibling = newNode;
+ } else {
+ Debug.Assert (parent.firstChild == this);
+ parent.firstChild = newNode;
+ }
+ if (nextSibling != null) {
+ Debug.Assert (nextSibling.prevSibling == this);
+ nextSibling.prevSibling = newNode;
+ } else {
+ Debug.Assert (parent.lastChild == this);
+ parent.lastChild = newNode;
+ }
+ parent = null;
+ prevSibling = null;
+ nextSibling = null;
+ }
+
+ public AstNode ReplaceWith (Func replaceFunction)
+ {
+ if (replaceFunction == null)
+ throw new ArgumentNullException ("replaceFunction");
+ if (parent == null) {
+ throw new InvalidOperationException (this.IsNull ? "Cannot replace the null nodes" : "Cannot replace the root node");
+ }
+ AstNode oldParent = parent;
+ AstNode oldSuccessor = nextSibling;
+ Role oldRole = this.Role;
+ Remove ();
+ AstNode replacement = replaceFunction (this);
+ if (oldSuccessor != null && oldSuccessor.parent != oldParent)
+ throw new InvalidOperationException ("replace function changed nextSibling of node being replaced?");
+ if (!(replacement == null || replacement.IsNull)) {
+ if (replacement.parent != null)
+ throw new InvalidOperationException ("replace function must return the root of a tree");
+ if (!oldRole.IsValid (replacement)) {
+ throw new InvalidOperationException (string.Format ("The new node '{0}' is not valid in the role {1}", replacement.GetType ().Name, oldRole.ToString ()));
+ }
+
+ if (oldSuccessor != null)
+ oldParent.InsertChildBeforeUnsafe (oldSuccessor, replacement, oldRole);
+ else
+ oldParent.AddChildUnsafe (replacement, oldRole);
+ }
+ return replacement;
+ }
+
+ ///
+ /// Clones the whole subtree starting at this AST node.
+ ///
+ /// Annotations are copied over to the new nodes; and any annotations implementing ICloneable will be cloned.
+ public AstNode Clone ()
+ {
+ AstNode copy = (AstNode)MemberwiseClone ();
+ // First, reset the shallow pointer copies
+ copy.parent = null;
+ copy.firstChild = null;
+ copy.lastChild = null;
+ copy.prevSibling = null;
+ copy.nextSibling = null;
+ copy.flags &= ~frozenBit; // unfreeze the copy
+
+ // Then perform a deep copy:
+ for (AstNode cur = firstChild; cur != null; cur = cur.nextSibling) {
+ copy.AddChildUnsafe (cur.Clone (), cur.Role);
+ }
+
+ // Finally, clone the annotation, if necessary
+ copy.CloneAnnotations();
+
+ return copy;
+ }
+
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public abstract void AcceptVisitor (IAstVisitor visitor);
+
+ public abstract T AcceptVisitor (IAstVisitor visitor);
+
+ public abstract S AcceptVisitor (IAstVisitor visitor, T data);
+
+ #region Pattern Matching
+ protected static bool MatchString (string pattern, string text)
+ {
+ return PatternMatching.Pattern.MatchString(pattern, text);
+ }
+
+ protected internal abstract bool DoMatch (AstNode other, PatternMatching.Match match);
+
+ bool PatternMatching.INode.DoMatch (PatternMatching.INode other, PatternMatching.Match match)
+ {
+ AstNode o = other as AstNode;
+ // try matching if other is null, or if other is an AstNode
+ return (other == null || o != null) && DoMatch (o, match);
+ }
+
+ bool PatternMatching.INode.DoMatchCollection (Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo)
+ {
+ AstNode o = pos as AstNode;
+ return (pos == null || o != null) && DoMatch (o, match);
+ }
+
+ PatternMatching.INode PatternMatching.INode.NextSibling {
+ get { return nextSibling; }
+ }
+
+ PatternMatching.INode PatternMatching.INode.FirstChild {
+ get { return firstChild; }
+ }
+
+ #endregion
+
+ public AstNode GetNextNode ()
+ {
+ if (NextSibling != null)
+ return NextSibling;
+ if (Parent != null)
+ return Parent.GetNextNode ();
+ return null;
+ }
+
+ ///
+ /// Gets the next node which fullfills a given predicate
+ ///
+ /// The next node.
+ /// The predicate.
+ public AstNode GetNextNode (Func pred)
+ {
+ var next = GetNextNode();
+ while (next != null && !pred (next))
+ next = next.GetNextNode();
+ return next;
+ }
+
+ public AstNode GetPrevNode ()
+ {
+ if (PrevSibling != null)
+ return PrevSibling;
+ if (Parent != null)
+ return Parent.GetPrevNode ();
+ return null;
+ }
+
+ ///
+ /// Gets the previous node which fullfills a given predicate
+ ///
+ /// The next node.
+ /// The predicate.
+ public AstNode GetPrevNode (Func pred)
+ {
+ var prev = GetPrevNode();
+ while (prev != null && !pred (prev))
+ prev = prev.GetPrevNode();
+ return prev;
+ }
+ // filters all non c# nodes (comments, white spaces or pre processor directives)
+ public AstNode GetCSharpNodeBefore (AstNode node)
+ {
+ var n = node.PrevSibling;
+ while (n != null) {
+ if (n.Role != Roles.Comment)
+ return n;
+ n = n.GetPrevNode ();
+ }
+ return null;
+ }
+
+ ///
+ /// Gets the next sibling which fullfills a given predicate
+ ///
+ /// The next node.
+ /// The predicate.
+ public AstNode GetNextSibling (Func pred)
+ {
+ var next = NextSibling;
+ while (next != null && !pred (next))
+ next = next.NextSibling;
+ return next;
+ }
+
+ ///
+ /// Gets the next sibling which fullfills a given predicate
+ ///
+ /// The next node.
+ /// The predicate.
+ public AstNode GetPrevSibling (Func pred)
+ {
+ var prev = PrevSibling;
+ while (prev != null && !pred (prev))
+ prev = prev.PrevSibling;
+ return prev;
+ }
+
+ #region GetNodeAt
+ ///
+ /// Gets the node specified by T at the location line, column. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End exclusive)
+ ///
+ public AstNode GetNodeAt (int line, int column, Predicate pred = null)
+ {
+ return GetNodeAt (new TextLocation (line, column), pred);
+ }
+
+ ///
+ /// Gets the node specified by pred at location. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End exclusive)
+ ///
+ public AstNode GetNodeAt (TextLocation location, Predicate pred = null)
+ {
+ AstNode result = null;
+ AstNode node = this;
+ while (node.LastChild != null) {
+ var child = node.LastChild;
+ while (child != null && child.StartLocation > location)
+ child = child.prevSibling;
+ if (child != null && location < child.EndLocation) {
+ if (pred == null || pred (child))
+ result = child;
+ node = child;
+ } else {
+ // found no better child node - therefore the parent is the right one.
+ break;
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// Gets the node specified by T at the location line, column. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End exclusive)
+ ///
+ public T GetNodeAt (int line, int column) where T : AstNode
+ {
+ return GetNodeAt (new TextLocation (line, column));
+ }
+
+ ///
+ /// Gets the node specified by T at location. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End exclusive)
+ ///
+ public T GetNodeAt (TextLocation location) where T : AstNode
+ {
+ T result = null;
+ AstNode node = this;
+ while (node.LastChild != null) {
+ var child = node.LastChild;
+ while (child != null && child.StartLocation > location)
+ child = child.prevSibling;
+ if (child != null && location < child.EndLocation) {
+ if (child is T)
+ result = (T)child;
+ node = child;
+ } else {
+ // found no better child node - therefore the parent is the right one.
+ break;
+ }
+ }
+ return result;
+ }
+
+ #endregion
+
+ #region GetAdjacentNodeAt
+ ///
+ /// Gets the node specified by pred at the location line, column. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End inclusive)
+ ///
+ public AstNode GetAdjacentNodeAt(int line, int column, Predicate pred = null)
+ {
+ return GetAdjacentNodeAt (new TextLocation (line, column), pred);
+ }
+
+ ///
+ /// Gets the node specified by pred at location. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End inclusive)
+ ///
+ public AstNode GetAdjacentNodeAt (TextLocation location, Predicate pred = null)
+ {
+ AstNode result = null;
+ AstNode node = this;
+ while (node.LastChild != null) {
+ var child = node.LastChild;
+ while (child != null && child.StartLocation > location)
+ child = child.prevSibling;
+ if (child != null && location <= child.EndLocation) {
+ if (pred == null || pred (child))
+ result = child;
+ node = child;
+ } else {
+ // found no better child node - therefore the parent is the right one.
+ break;
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// Gets the node specified by T at the location line, column. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End inclusive)
+ ///
+ public T GetAdjacentNodeAt(int line, int column) where T : AstNode
+ {
+ return GetAdjacentNodeAt (new TextLocation (line, column));
+ }
+
+ ///
+ /// Gets the node specified by T at location. This is useful for getting a specific node from the tree. For example searching
+ /// the current method declaration.
+ /// (End inclusive)
+ ///
+ public T GetAdjacentNodeAt (TextLocation location) where T : AstNode
+ {
+ T result = null;
+ AstNode node = this;
+ while (node.LastChild != null) {
+ var child = node.LastChild;
+ while (child != null && child.StartLocation > location)
+ child = child.prevSibling;
+ if (child != null && location <= child.EndLocation) {
+ if (child is T)
+ result = (T)child;
+ node = child;
+ } else {
+ // found no better child node - therefore the parent is the right one.
+ break;
+ }
+ }
+ return result;
+ }
+ #endregion
+
+
+ ///
+ /// Gets the node that fully contains the range from startLocation to endLocation.
+ ///
+ public AstNode GetNodeContaining(TextLocation startLocation, TextLocation endLocation)
+ {
+ for (AstNode child = firstChild; child != null; child = child.nextSibling) {
+ if (child.StartLocation <= startLocation && endLocation <= child.EndLocation)
+ return child.GetNodeContaining(startLocation, endLocation);
+ }
+ return this;
+ }
+
+ ///
+ /// Returns the root nodes of all subtrees that are fully contained in the specified region.
+ ///
+ public IEnumerable GetNodesBetween (int startLine, int startColumn, int endLine, int endColumn)
+ {
+ return GetNodesBetween (new TextLocation (startLine, startColumn), new TextLocation (endLine, endColumn));
+ }
+
+ ///
+ /// Returns the root nodes of all subtrees that are fully contained between and (inclusive).
+ ///
+ public IEnumerable GetNodesBetween (TextLocation start, TextLocation end)
+ {
+ AstNode node = this;
+ while (node != null) {
+ AstNode next;
+ if (start <= node.StartLocation && node.EndLocation <= end) {
+ // Remember next before yielding node.
+ // This allows iteration to continue when the caller removes/replaces the node.
+ next = node.GetNextNode();
+ yield return node;
+ } else {
+ if (node.EndLocation <= start) {
+ next = node.GetNextNode();
+ } else {
+ next = node.FirstChild;
+ }
+ }
+
+ if (next != null && next.StartLocation > end)
+ yield break;
+ node = next;
+ }
+ }
+ [Obsolete("Use ToString(options).")]
+ public string GetText (CSharpFormattingOptions formattingOptions = null)
+ {
+ return ToString(formattingOptions);
+ }
+
+ ///
+ /// Gets the node as formatted C# output.
+ ///
+ ///
+ /// Formatting options.
+ ///
+ public virtual string ToString (CSharpFormattingOptions formattingOptions)
+ {
+ if (IsNull)
+ return "";
+ var w = new StringWriter ();
+ AcceptVisitor (new CSharpOutputVisitor (w, formattingOptions ?? FormattingOptionsFactory.CreateMono ()));
+ return w.ToString ();
+ }
+
+ public sealed override string ToString()
+ {
+ return ToString(null);
+ }
+
+ ///
+ /// Returns true, if the given coordinates (line, column) are in the node.
+ ///
+ ///
+ /// True, if the given coordinates are between StartLocation and EndLocation (exclusive); otherwise, false.
+ ///
+ public bool Contains (int line, int column)
+ {
+ return Contains (new TextLocation (line, column));
+ }
+
+ ///
+ /// Returns true, if the given coordinates are in the node.
+ ///
+ ///
+ /// True, if location is between StartLocation and EndLocation (exclusive); otherwise, false.
+ ///
+ public bool Contains (TextLocation location)
+ {
+ return this.StartLocation <= location && location < this.EndLocation;
+ }
+
+ ///
+ /// Returns true, if the given coordinates (line, column) are in the node.
+ ///
+ ///
+ /// True, if the given coordinates are between StartLocation and EndLocation (inclusive); otherwise, false.
+ ///
+ public bool IsInside (int line, int column)
+ {
+ return IsInside (new TextLocation (line, column));
+ }
+
+ ///
+ /// Returns true, if the given coordinates are in the node.
+ ///
+ ///
+ /// True, if location is between StartLocation and EndLocation (inclusive); otherwise, false.
+ ///
+ public bool IsInside (TextLocation location)
+ {
+ return this.StartLocation <= location && location <= this.EndLocation;
+ }
+
+ public override void AddAnnotation (object annotation)
+ {
+ if (this.IsNull)
+ throw new InvalidOperationException ("Cannot add annotations to the null node");
+ base.AddAnnotation (annotation);
+ }
+
+ internal string DebugToString()
+ {
+ if (IsNull)
+ return "Null";
+ string text = ToString();
+ text = text.TrimEnd().Replace("\t", "").Replace(Environment.NewLine, " ");
+ if (text.Length > 100)
+ return text.Substring(0, 97) + "...";
+ else
+ return text;
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
new file mode 100644
index 000000000..32d08b2e4
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstNodeCollection.cs
@@ -0,0 +1,231 @@
+// Copyright (c) 2010-2013 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using ICSharpCode.NRefactory.PatternMatching;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ ///
+ /// Represents the children of an AstNode that have a specific role.
+ ///
+ public class AstNodeCollection : ICollection
+ #if NET_4_5
+ , IReadOnlyCollection
+ #endif
+ where T : AstNode
+ {
+ readonly AstNode node;
+ readonly Role role;
+
+ public AstNodeCollection(AstNode node, Role role)
+ {
+ if (node == null)
+ throw new ArgumentNullException("node");
+ if (role == null)
+ throw new ArgumentNullException("role");
+ this.node = node;
+ this.role = role;
+ }
+
+ public int Count {
+ get {
+ int count = 0;
+ uint roleIndex = role.Index;
+ for (AstNode cur = node.FirstChild; cur != null; cur = cur.NextSibling) {
+ if (cur.RoleIndex == roleIndex)
+ count++;
+ }
+ return count;
+ }
+ }
+
+ public void Add(T element)
+ {
+ node.AddChild(element, role);
+ }
+
+ public void AddRange(IEnumerable nodes)
+ {
+ // Evaluate 'nodes' first, since it might change when we add the new children
+ // Example: collection.AddRange(collection);
+ if (nodes != null) {
+ foreach (T node in nodes.ToList())
+ Add(node);
+ }
+ }
+
+ public void AddRange(T[] nodes)
+ {
+ // Fast overload for arrays - we don't need to create a copy
+ if (nodes != null) {
+ foreach (T node in nodes)
+ Add(node);
+ }
+ }
+
+ public void ReplaceWith(IEnumerable nodes)
+ {
+ // Evaluate 'nodes' first, since it might change when we call Clear()
+ // Example: collection.ReplaceWith(collection);
+ if (nodes != null)
+ nodes = nodes.ToList();
+ Clear();
+ if (nodes != null) {
+ foreach (T node in nodes)
+ Add(node);
+ }
+ }
+
+ public void MoveTo(ICollection targetCollection)
+ {
+ if (targetCollection == null)
+ throw new ArgumentNullException("targetCollection");
+ foreach (T node in this) {
+ node.Remove();
+ targetCollection.Add(node);
+ }
+ }
+
+ public bool Contains(T element)
+ {
+ return element != null && element.Parent == node && element.RoleIndex == role.Index;
+ }
+
+ public bool Remove(T element)
+ {
+ if (Contains(element)) {
+ element.Remove();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ foreach (T item in this)
+ array[arrayIndex++] = item;
+ }
+
+ public void Clear()
+ {
+ foreach (T item in this)
+ item.Remove();
+ }
+
+ ///
+ /// Returns the first element for which the predicate returns true,
+ /// or the null node (AstNode with IsNull=true) if no such object is found.
+ ///
+ public T FirstOrNullObject(Func predicate = null)
+ {
+ foreach (T item in this)
+ if (predicate == null || predicate(item))
+ return item;
+ return role.NullObject;
+ }
+
+ ///
+ /// Returns the last element for which the predicate returns true,
+ /// or the null node (AstNode with IsNull=true) if no such object is found.
+ ///
+ public T LastOrNullObject(Func predicate = null)
+ {
+ T result = role.NullObject;
+ foreach (T item in this)
+ if (predicate == null || predicate(item))
+ result = item;
+ return result;
+ }
+
+ bool ICollection.IsReadOnly {
+ get { return false; }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ uint roleIndex = role.Index;
+ AstNode next;
+ for (AstNode cur = node.FirstChild; cur != null; cur = next) {
+ Debug.Assert(cur.Parent == node);
+ // Remember next before yielding cur.
+ // This allows removing/replacing nodes while iterating through the list.
+ next = cur.NextSibling;
+ if (cur.RoleIndex == roleIndex)
+ yield return (T)cur;
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #region Equals and GetHashCode implementation
+ public override int GetHashCode()
+ {
+ return node.GetHashCode() ^ role.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ AstNodeCollection other = obj as AstNodeCollection;
+ if (other == null)
+ return false;
+ return this.node == other.node && this.role == other.role;
+ }
+ #endregion
+
+ internal bool DoMatch(AstNodeCollection other, Match match)
+ {
+ return Pattern.DoMatchCollection(role, node.FirstChild, other.node.FirstChild, match);
+ }
+
+ public void InsertAfter(T existingItem, T newItem)
+ {
+ node.InsertChildAfter(existingItem, newItem, role);
+ }
+
+ public void InsertBefore(T existingItem, T newItem)
+ {
+ node.InsertChildBefore(existingItem, newItem, role);
+ }
+
+ ///
+ /// Applies the to all nodes in this collection.
+ ///
+ public void AcceptVisitor(IAstVisitor visitor)
+ {
+ uint roleIndex = role.Index;
+ AstNode next;
+ for (AstNode cur = node.FirstChild; cur != null; cur = next) {
+ Debug.Assert(cur.Parent == node);
+ // Remember next before yielding cur.
+ // This allows removing/replacing nodes while iterating through the list.
+ next = cur.NextSibling;
+ if (cur.RoleIndex == roleIndex)
+ cur.AcceptVisitor(visitor);
+ }
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstType.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstType.cs
new file mode 100644
index 000000000..2f13f0fdd
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/AstType.cs
@@ -0,0 +1,280 @@
+// Copyright (c) 2010-2013 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 ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.TypeSystem;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ ///
+ /// A type reference in the C# AST.
+ ///
+ public abstract class AstType : AstNode
+ {
+ #region Null
+ public new static readonly AstType Null = new NullAstType ();
+
+ sealed class NullAstType : AstType
+ {
+ public override bool IsNull {
+ get {
+ return true;
+ }
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitNullNode(this);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitNullNode(this);
+ }
+
+ public override S AcceptVisitor (IAstVisitor visitor, T data)
+ {
+ return visitor.VisitNullNode(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
+ {
+ return other == null || other.IsNull;
+ }
+
+ public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider)
+ {
+ return SpecialType.UnknownType;
+ }
+ }
+ #endregion
+
+ #region PatternPlaceholder
+ public static implicit operator AstType(PatternMatching.Pattern pattern)
+ {
+ return pattern != null ? new PatternPlaceholder(pattern) : null;
+ }
+
+ sealed class PatternPlaceholder : AstType, PatternMatching.INode
+ {
+ readonly PatternMatching.Pattern child;
+
+ public PatternPlaceholder(PatternMatching.Pattern child)
+ {
+ this.child = child;
+ }
+
+ public override NodeType NodeType {
+ get { return NodeType.Pattern; }
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitPatternPlaceholder (this, child);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitPatternPlaceholder (this, child);
+ }
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {
+ return visitor.VisitPatternPlaceholder (this, child, data);
+ }
+
+ public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
+ {
+ return child.DoMatch(other, match);
+ }
+
+ bool PatternMatching.INode.DoMatchCollection(Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo)
+ {
+ return child.DoMatchCollection(role, pos, match, backtrackingInfo);
+ }
+ }
+ #endregion
+
+ public override NodeType NodeType {
+ get { return NodeType.TypeReference; }
+ }
+
+ public new AstType Clone()
+ {
+ return (AstType)base.Clone();
+ }
+
+ ///
+ /// Gets whether this type is a SimpleType "var".
+ ///
+ public bool IsVar()
+ {
+ SimpleType st = this as SimpleType;
+ return st != null && st.Identifier == "var" && st.TypeArguments.Count == 0;
+ }
+
+ ///
+ /// Create an ITypeReference for this AstType.
+ /// Uses the context (ancestors of this node) to determine the correct .
+ ///
+ ///
+ /// The resulting type reference will read the context information from the
+ /// :
+ /// For resolving type parameters, the CurrentTypeDefinition/CurrentMember is used.
+ /// For resolving simple names, the current namespace and usings from the CurrentUsingScope
+ /// (on CSharpTypeResolveContext only) is used.
+ ///
+ public ITypeReference ToTypeReference(InterningProvider interningProvider = null)
+ {
+ return ToTypeReference(GetNameLookupMode(), interningProvider);
+ }
+
+ ///
+ /// Create an ITypeReference for this AstType.
+ ///
+ ///
+ /// The resulting type reference will read the context information from the
+ /// :
+ /// For resolving type parameters, the CurrentTypeDefinition/CurrentMember is used.
+ /// For resolving simple names, the current namespace and usings from the CurrentUsingScope
+ /// (on CSharpTypeResolveContext only) is used.
+ ///
+ public abstract ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null);
+
+ ///
+ /// Gets the name lookup mode from the context (looking at the ancestors of this ).
+ ///
+ public NameLookupMode GetNameLookupMode()
+ {
+ AstType outermostType = this;
+ while (outermostType.Parent is AstType)
+ outermostType = (AstType)outermostType.Parent;
+
+ if (outermostType.Parent is UsingDeclaration || outermostType.Parent is UsingAliasDeclaration) {
+ return NameLookupMode.TypeInUsingDeclaration;
+ } else if (outermostType.Role == Roles.BaseType) {
+ // Use BaseTypeReference for a type's base type, and for a constraint on a type.
+ // Do not use it for a constraint on a method.
+ if (outermostType.Parent is TypeDeclaration || (outermostType.Parent is Constraint && outermostType.Parent.Parent is TypeDeclaration))
+ return NameLookupMode.BaseTypeReference;
+ }
+ return NameLookupMode.Type;
+ }
+
+ ///
+ /// Creates a pointer type from this type by nesting it in a .
+ /// If this type already is a pointer type, this method just increases the PointerRank of the existing pointer type.
+ ///
+ public virtual AstType MakePointerType()
+ {
+ return new ComposedType { BaseType = this }.MakePointerType();
+ }
+
+ ///
+ /// Creates an array type from this type by nesting it in a .
+ /// If this type already is an array type, the additional rank is prepended to the existing array specifier list.
+ /// Thus, new SimpleType("T").MakeArrayType(1).MakeArrayType(2) will result in "T[,][]".
+ ///
+ public virtual AstType MakeArrayType(int rank = 1)
+ {
+ return new ComposedType { BaseType = this }.MakeArrayType(rank);
+ }
+
+ ///
+ /// Creates a nullable type from this type by nesting it in a .
+ ///
+ public AstType MakeNullableType()
+ {
+ return new ComposedType { BaseType = this, HasNullableSpecifier = true };
+ }
+
+ ///
+ /// Builds an expression that can be used to access a static member on this type.
+ ///
+ public MemberReferenceExpression Member(string memberName)
+ {
+ return new TypeReferenceExpression { Type = this }.Member(memberName);
+ }
+
+ ///
+ /// Builds an expression that can be used to access a static member on this type.
+ ///
+ public MemberType MemberType(string memberName, params AstType[] typeArguments)
+ {
+ var memberType = new MemberType(this, memberName);
+ memberType.TypeArguments.AddRange(typeArguments);
+ return memberType;
+ }
+
+ ///
+ /// Builds an expression that can be used to access a static member on this type.
+ ///
+ public MemberType MemberType(string memberName, IEnumerable typeArguments)
+ {
+ var memberType = new MemberType(this, memberName);
+ memberType.TypeArguments.AddRange(typeArguments);
+ return memberType;
+ }
+
+ ///
+ /// Builds an invocation expression using this type as target.
+ ///
+ public InvocationExpression Invoke(string methodName, IEnumerable arguments)
+ {
+ return new TypeReferenceExpression { Type = this }.Invoke(methodName, arguments);
+ }
+
+ ///
+ /// Builds an invocation expression using this type as target.
+ ///
+ public InvocationExpression Invoke(string methodName, params Expression[] arguments)
+ {
+ return new TypeReferenceExpression { Type = this }.Invoke(methodName, arguments);
+ }
+
+ ///
+ /// Builds an invocation expression using this type as target.
+ ///
+ public InvocationExpression Invoke(string methodName, IEnumerable typeArguments, IEnumerable arguments)
+ {
+ return new TypeReferenceExpression { Type = this }.Invoke(methodName, typeArguments, arguments);
+ }
+
+ ///
+ /// Creates a simple AstType from a dotted name.
+ /// Does not support generics, arrays, etc. - just simple dotted names,
+ /// e.g. namespace names.
+ ///
+ public static AstType Create(string dottedName)
+ {
+ string[] parts = dottedName.Split('.');
+ AstType type = new SimpleType(parts[0]);
+ for (int i = 1; i < parts.Length; i++) {
+ type = new MemberType(type, parts[i]);
+ }
+ return type;
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs
new file mode 100644
index 000000000..1a46006f2
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpModifierToken.cs
@@ -0,0 +1,218 @@
+//
+// CSharpModifierToken.cs
+//
+// Author:
+// Mike Krüger
+//
+// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
+//
+// 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;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ public class CSharpModifierToken : CSharpTokenNode
+ {
+ Modifiers modifier;
+
+ public Modifiers Modifier {
+ get { return modifier; }
+ set {
+ ThrowIfFrozen();
+ this.modifier = value;
+ }
+ }
+
+ public override TextLocation EndLocation {
+ get {
+ return new TextLocation (StartLocation.Line, StartLocation.Column + GetModifierLength (Modifier));
+ }
+ }
+
+ public override string ToString(CSharpFormattingOptions formattingOptions)
+ {
+ return GetModifierName (Modifier);
+ }
+
+ protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
+ {
+ CSharpModifierToken o = other as CSharpModifierToken;
+ return o != null && this.modifier == o.modifier;
+ }
+
+ // Not worth using a dictionary for such few elements.
+ // This table is sorted in the order that modifiers should be output when generating code.
+ static readonly Modifiers[] allModifiers = {
+ Modifiers.Public, Modifiers.Protected, Modifiers.Private, Modifiers.Internal,
+ Modifiers.New,
+ Modifiers.Unsafe,
+ Modifiers.Abstract, Modifiers.Virtual, Modifiers.Sealed, Modifiers.Static, Modifiers.Override,
+ Modifiers.Readonly, Modifiers.Volatile,
+ Modifiers.Extern, Modifiers.Partial, Modifiers.Const,
+ Modifiers.Async,
+ Modifiers.Any
+ };
+
+ public static IEnumerable AllModifiers {
+ get { return allModifiers; }
+ }
+
+ public CSharpModifierToken (TextLocation location, Modifiers modifier) : base (location, null)
+ {
+ this.Modifier = modifier;
+ }
+
+ public static string GetModifierName(Modifiers modifier)
+ {
+ switch (modifier) {
+ case Modifiers.Private:
+ return "private";
+ case Modifiers.Internal:
+ return "internal";
+ case Modifiers.Protected:
+ return "protected";
+ case Modifiers.Public:
+ return "public";
+ case Modifiers.Abstract:
+ return "abstract";
+ case Modifiers.Virtual:
+ return "virtual";
+ case Modifiers.Sealed:
+ return "sealed";
+ case Modifiers.Static:
+ return "static";
+ case Modifiers.Override:
+ return "override";
+ case Modifiers.Readonly:
+ return "readonly";
+ case Modifiers.Const:
+ return "const";
+ case Modifiers.New:
+ return "new";
+ case Modifiers.Partial:
+ return "partial";
+ case Modifiers.Extern:
+ return "extern";
+ case Modifiers.Volatile:
+ return "volatile";
+ case Modifiers.Unsafe:
+ return "unsafe";
+ case Modifiers.Async:
+ return "async";
+ case Modifiers.Any:
+ // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST
+ return "any";
+ default:
+ throw new NotSupportedException("Invalid value for Modifiers");
+ }
+ }
+
+ public static int GetModifierLength(Modifiers modifier)
+ {
+ switch (modifier) {
+ case Modifiers.Private:
+ return "private".Length;
+ case Modifiers.Internal:
+ return "internal".Length;
+ case Modifiers.Protected:
+ return "protected".Length;
+ case Modifiers.Public:
+ return "public".Length;
+ case Modifiers.Abstract:
+ return "abstract".Length;
+ case Modifiers.Virtual:
+ return "virtual".Length;
+ case Modifiers.Sealed:
+ return "sealed".Length;
+ case Modifiers.Static:
+ return "static".Length;
+ case Modifiers.Override:
+ return "override".Length;
+ case Modifiers.Readonly:
+ return "readonly".Length;
+ case Modifiers.Const:
+ return "const".Length;
+ case Modifiers.New:
+ return "new".Length;
+ case Modifiers.Partial:
+ return "partial".Length;
+ case Modifiers.Extern:
+ return "extern".Length;
+ case Modifiers.Volatile:
+ return "volatile".Length;
+ case Modifiers.Unsafe:
+ return "unsafe".Length;
+ case Modifiers.Async:
+ return "async".Length;
+ case Modifiers.Any:
+ // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST
+ return "any".Length;
+ default:
+ throw new NotSupportedException("Invalid value for Modifiers");
+ }
+ }
+
+ public static Modifiers GetModifierValue(string modifier)
+ {
+ switch (modifier) {
+ case "private":
+ return Modifiers.Private;
+ case "internal":
+ return Modifiers.Internal;
+ case "protected":
+ return Modifiers.Protected;
+ case "public":
+ return Modifiers.Public;
+ case "abstract":
+ return Modifiers.Abstract;
+ case "virtual":
+ return Modifiers.Virtual;
+ case "sealed":
+ return Modifiers.Sealed;
+ case "static":
+ return Modifiers.Static;
+ case "override":
+ return Modifiers.Override;
+ case "readonly":
+ return Modifiers.Readonly;
+ case "const":
+ return Modifiers.Const;
+ case "new":
+ return Modifiers.New;
+ case "partial":
+ return Modifiers.Partial;
+ case "extern":
+ return Modifiers.Extern;
+ case "volatile":
+ return Modifiers.Volatile;
+ case "unsafe":
+ return Modifiers.Unsafe;
+ case "async":
+ return Modifiers.Async;
+ case "any":
+ // even though it's used for pattern matching only, 'any' needs to be in this list to be usable in the AST
+ return Modifiers.Any;
+ default:
+ throw new NotSupportedException("Invalid value for Modifiers");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpTokenNode.cs b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpTokenNode.cs
new file mode 100644
index 000000000..713f664b3
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.CSharp/Ast/CSharpTokenNode.cs
@@ -0,0 +1,131 @@
+//
+// TokenNode.cs
+//
+// Author:
+// Mike Krüger
+//
+// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
+//
+// 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;
+
+namespace ICSharpCode.NRefactory.CSharp
+{
+ ///
+ /// Represents a token in C#. Note that the type of the token is defined through the TokenRole.
+ ///
+ ///
+ /// In all non null c# token nodes the Role of a CSharpToken must be a TokenRole.
+ ///
+ public class CSharpTokenNode : AstNode
+ {
+ public static new readonly CSharpTokenNode Null = new NullCSharpTokenNode ();
+ class NullCSharpTokenNode : CSharpTokenNode
+ {
+ public override bool IsNull {
+ get {
+ return true;
+ }
+ }
+
+ public NullCSharpTokenNode () : base (TextLocation.Empty, null)
+ {
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitNullNode(this);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitNullNode(this);
+ }
+
+ public override S AcceptVisitor (IAstVisitor visitor, T data)
+ {
+ return visitor.VisitNullNode(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
+ {
+ return other == null || other.IsNull;
+ }
+ }
+
+ public override NodeType NodeType {
+ get {
+ return NodeType.Token;
+ }
+ }
+
+ TextLocation startLocation;
+ public override TextLocation StartLocation {
+ get {
+ return startLocation;
+ }
+ }
+
+ int TokenLength {
+ get {
+ return TokenRole.TokenLengths [(int)(this.flags >> AstNodeFlagsUsedBits)];
+ }
+ }
+
+ public override TextLocation EndLocation {
+ get {
+ return new TextLocation (StartLocation.Line, StartLocation.Column + TokenLength);
+ }
+ }
+
+ public CSharpTokenNode (TextLocation location, TokenRole role)
+ {
+ this.startLocation = location;
+ if (role != null)
+ this.flags |= role.TokenIndex << AstNodeFlagsUsedBits;
+ }
+
+ public override string ToString(CSharpFormattingOptions formattingOptions)
+ {
+ return TokenRole.Tokens [(int)(this.flags >> AstNodeFlagsUsedBits)];
+ }
+
+ public override void AcceptVisitor (IAstVisitor visitor)
+ {
+ visitor.VisitCSharpTokenNode (this);
+ }
+
+ public override T AcceptVisitor (IAstVisitor visitor)
+ {
+ return visitor.VisitCSharpTokenNode (this);
+ }
+
+ public override S AcceptVisitor