|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class MemberSemanticModel
{
protected sealed class NodeMapBuilder : BoundTreeWalkerWithStackGuard
{
private NodeMapBuilder(OrderPreservingMultiDictionary<SyntaxNode, BoundNode> map, SyntaxTree tree, SyntaxNode thisSyntaxNodeOnly)
{
_map = map;
_tree = tree;
_thisSyntaxNodeOnly = thisSyntaxNodeOnly;
}
private readonly OrderPreservingMultiDictionary<SyntaxNode, BoundNode> _map;
private readonly SyntaxTree _tree;
private readonly SyntaxNode _thisSyntaxNodeOnly;
/// <summary>
/// Walks the bound tree and adds all non compiler generated bound nodes whose syntax matches the given one
/// to the cache.
/// </summary>
/// <param name="root">The root of the bound tree.</param>
/// <param name="map">The cache.</param>
/// <param name="node">The syntax node where to add bound nodes for.</param>
public static void AddToMap(BoundNode root, Dictionary<SyntaxNode, OneOrMany<BoundNode>> map, SyntaxTree tree, SyntaxNode node = null)
{
Debug.Assert(node == null || root == null || !(root.Syntax is StatementSyntax), "individually added nodes are not supposed to be statements.");
if (root == null || map.ContainsKey(root.Syntax))
{
// root node is already in the map, children must be in the map too.
return;
}
var additionMap = OrderPreservingMultiDictionary<SyntaxNode, BoundNode>.GetInstance();
var builder = new NodeMapBuilder(additionMap, tree, node);
builder.Visit(root);
#if NET
// Ensure map is large enough to hold found nodes.
map.EnsureCapacity(map.Count + additionMap.Keys.Count);
#endif
foreach (CSharpSyntaxNode key in additionMap.Keys)
{
var nodesToAdd = additionMap.GetAsOneOrMany(key);
if (!map.TryAdd(key, nodesToAdd))
{
#if DEBUG
// It's possible that AddToMap was previously called with a subtree of root. If this is the case,
// then we'll see an entry in the map. Since the incremental binder should also have seen the
// pre-existing map entry, the entry in addition map should be identical.
// Another, more unfortunate, possibility is that we've had to re-bind the syntax and the new bound
// nodes are equivalent, but not identical, to the existing ones. In such cases, we prefer the
// existing nodes so that the cache will always return the same bound node for a given syntax node.
// EXAMPLE: Suppose we have the statement P.M(1);
// First, we ask for semantic info about "P". We'll walk up to the statement level and bind that.
// We'll end up with map entries for "1", "P", "P.M(1)", and "P.M(1);".
// Next, we ask for semantic info about "P.M". That isn't in our map, so we walk up to the statement
// level - again - and bind that - again.
// Once again, we'll end up with map entries for "1", "P", "P.M(1)", and "P.M(1);". They will
// have the same structure as the original map entries, but will not be ReferenceEquals.
var existing = map[key];
Debug.Assert(existing.Count == nodesToAdd.Count, "existing.Count == nodesToAdd.Length");
for (int i = 0; i < existing.Count; i++)
{
// TODO: it would be great if we could check !ReferenceEquals(existing[i], nodesToAdd[i]) (DevDiv #11584).
// Known impediments include:
// 1) Field initializers aren't cached because they're not in statements.
// 2) Single local declarations (e.g. "int x = 1;" vs "int x = 1, y = 2;") aren't found in the cache
// since nothing is cached for the statement syntax.
if (existing[i].Kind != nodesToAdd[i].Kind)
{
Debug.Assert(!(key is StatementSyntax), "!(key is StatementSyntax)");
// This also seems to be happening when we get equivalent BoundTypeExpression and BoundTypeOrValueExpression nodes.
if (existing[i].Kind == BoundKind.TypeExpression && nodesToAdd[i].Kind == BoundKind.TypeOrValueExpression)
{
Debug.Assert(
TypeSymbol.Equals(((BoundTypeExpression)existing[i]).Type, ((BoundTypeOrValueExpression)nodesToAdd[i]).Type, TypeCompareKind.ConsiderEverything2),
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"((BoundTypeExpression)existing[{0}]).Type == ((BoundTypeOrValueExpression)nodesToAdd[{0}]).Type", i));
}
else if (existing[i].Kind == BoundKind.TypeOrValueExpression && nodesToAdd[i].Kind == BoundKind.TypeExpression)
{
Debug.Assert(
TypeSymbol.Equals(((BoundTypeOrValueExpression)existing[i]).Type, ((BoundTypeExpression)nodesToAdd[i]).Type, TypeCompareKind.ConsiderEverything2),
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"((BoundTypeOrValueExpression)existing[{0}]).Type == ((BoundTypeExpression)nodesToAdd[{0}]).Type", i));
}
else
{
Debug.Assert(false, "New bound node does not match existing bound node");
}
}
else
{
Debug.Assert(
(object)existing[i] == nodesToAdd[i] || !(key is StatementSyntax),
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"(object)existing[{0}] == nodesToAdd[{0}] || !(key is StatementSyntax)", i));
}
}
#endif
}
}
additionMap.Free();
}
public override BoundNode Visit(BoundNode node)
{
// Do not cache bound nodes associated with a different syntax tree. We can get nodes like that
// when semantic model binds complete simple program body. Semantic model is never asked about
// information for nodes associated with a different syntax tree, therefore, there is no advantage
// in putting the bound nodes in the map. SimpleProgramBodySemanticModelMergedBoundNodeCache facilitates
// reuse of bound nodes from other trees.
if (node == null || node.SyntaxTree != _tree)
{
return null;
}
BoundNode current = node;
// It is possible that we will encounter a lambda in the bound tree that was never
// turned into a bound lambda. For example, if you have something like:
//
// object x = (int y)=>M(y);
//
// then no conversion to a valid delegate type was performed and the "bound" tree
// contains an "unbound" lambda with no body. Or, similarly, we could have something
// like:
//
// M(x=>x.Blah());
//
// and overload resolution failed to infer a unique type for x; perhaps it
// inferred two types and neither return type was better. Or perhaps
// there was a semantic error inside the lambda.
//
// In any of these cases there will be no bound lambda in the bound tree.
//
// Ultimately what we probably want to do here is ensure that this never happens;
// we could run a post-processing pass on the bound tree, look for unbound lambdas,
// and replace them with bound lambdas. However at this time that is a fairly complex
// change and it is not clear where the most efficient place to put the rewriter is
// in the IDE scenario. Until we figure that out, I'm going to detect that situation
// here, when building the map. If we encounter an unbound lambda in the tree,
// we'll just bind it right now. The unbound lambda will cache the result, and we'll
// keep on trucking just as though there had been a bound lambda here all along.
if (node.Kind == BoundKind.UnboundLambda)
{
current = ((UnboundLambda)node).BindForErrorRecovery();
}
// It is possible for there to be multiple bound nodes with the same syntax tree,
// and that is by design. For example, in
//
// byte b = 3;
//
// there is a bound node for the implicit conversion to byte and a bound node for the
// literal, an int. Sometimes we want the inner one (to state the type of the expression)
// and sometimes we want the "parent's" view of things (for extract method, for instance.)
//
// We want to add all bound nodes associated with the same syntax node to the cache, so we first add the
// bound node, then we dive deeper into the bound tree.
if (ShouldAddNode(current))
{
_map.Add(current.Syntax, current);
}
// In machine-generated code we frequently end up with binary operator or pattern trees that are deep on the left,
// such as a + b + c + d ...
// To avoid blowing the call stack, we build an explicit stack to handle the left-hand recursion.
if (current is BoundBinaryOperator binOp)
{
var stack = ArrayBuilder<BoundExpression>.GetInstance();
stack.Push(binOp.Right);
current = binOp.Left;
binOp = current as BoundBinaryOperator;
while (binOp != null)
{
if (ShouldAddNode(binOp))
{
_map.Add(binOp.Syntax, binOp);
}
stack.Push(binOp.Right);
current = binOp.Left;
binOp = current as BoundBinaryOperator;
}
Visit(current);
while (stack.Count > 0)
{
Visit(stack.Pop());
}
stack.Free();
}
else if (current is BoundBinaryPattern binaryPattern)
{
var stack = ArrayBuilder<BoundPattern>.GetInstance();
stack.Push(binaryPattern.Right);
BoundPattern currentPattern = binaryPattern.Left;
binaryPattern = currentPattern as BoundBinaryPattern;
while (binaryPattern != null)
{
if (ShouldAddNode(binaryPattern))
{
_map.Add(binaryPattern.Syntax, binaryPattern);
}
stack.Push(binaryPattern.Right);
currentPattern = binaryPattern.Left;
binaryPattern = currentPattern as BoundBinaryPattern;
}
do
{
Visit(currentPattern);
} while (stack.TryPop(out currentPattern));
stack.Free();
}
else
{
base.Visit(current);
}
return null;
}
/// <summary>
/// Decides whether to the add the bound node to the cache or not.
/// </summary>
/// <param name="currentBoundNode">The bound node.</param>
private bool ShouldAddNode(BoundNode currentBoundNode)
{
// Do not add compiler generated nodes.
if (currentBoundNode.WasCompilerGenerated)
{
return false;
}
// Do not add if only a specific syntax node should be added.
if (_thisSyntaxNodeOnly != null && currentBoundNode.Syntax != _thisSyntaxNodeOnly)
{
return false;
}
return true;
}
public override BoundNode VisitQueryClause(BoundQueryClause node)
{
this.Visit(node.Value);
VisitUnoptimizedForm(node);
return null;
}
public override BoundNode VisitRangeVariable(BoundRangeVariable node)
{
return null;
}
public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node)
{
return null;
}
public override BoundNode VisitConstructorMethodBody(BoundConstructorMethodBody node)
{
// The implicit base call shares a syntax node with the constructor body, and we don't want to
// put it as the lower bound node as it will give incorrect results.
if (node.Syntax != node.Initializer?.Syntax)
{
this.Visit(node.Initializer);
}
this.Visit(node.BlockBody);
this.Visit(node.ExpressionBody);
return null;
}
public override BoundNode VisitBinaryOperator(BoundBinaryOperator node)
{
throw ExceptionUtilities.Unreachable();
}
public override BoundNode VisitIfStatement(BoundIfStatement node)
{
while (true)
{
this.Visit(node.Condition);
this.Visit(node.Consequence);
var alternative = node.AlternativeOpt;
if (alternative is null)
{
break;
}
if (alternative is BoundIfStatement elseIfStatement)
{
node = elseIfStatement;
if (ShouldAddNode(node))
{
_map.Add(node.Syntax, node);
}
}
else
{
this.Visit(alternative);
break;
}
}
return null;
}
protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByStackGuardException()
{
return false;
}
}
}
}
|