diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 034e9145085db6d5742f8ff2813c2b7396cd0349..56721983bbcafac774008db5ca39a132faf81a9a 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -51,7 +51,7 @@ protected MemberSemanticModel(CSharpCompilation compilation, CSharpSyntaxNode ro _parentSemanticModelOpt = parentSemanticModelOpt; _speculatedPosition = speculatedPosition; - _operationFactory = new CSharpOperationFactory(); + _operationFactory = new CSharpOperationFactory(this); } public override CSharpCompilation Compilation @@ -983,6 +983,10 @@ internal override IOperation GetOperationWorker(CSharpSyntaxNode node, GetOperat break; } + // The CSharp operation factory assumes that UnboundLambda will be bound for error recovery and never be passed to the factory + // as the start of a tree to get operations for. This is guaranteed by the builder that populates the node map, as it will call + // UnboundLambda.BindForErrorRecovery() when it encounters an UnboundLambda node. + Debug.Assert(result.Kind != BoundKind.UnboundLambda); return _operationFactory.Create(result); } diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 48e9f0fda67cb796fb204ac0518675d43828b1cf..b98308df6cd504d440d0348319dcbe71f76b74ad 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -13,6 +14,12 @@ internal sealed partial class CSharpOperationFactory { private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); + private readonly SemanticModel _semanticModel; + + public CSharpOperationFactory(SemanticModel semanticModel) + { + _semanticModel = semanticModel; + } public IOperation Create(BoundNode boundNode) { @@ -353,18 +360,15 @@ private IObjectCreationExpression CreateBoundObjectCreationExpressionOperation(B return new LazyObjectCreationExpression(constructor, memberInitializers, argumentsInEvaluationOrder, isInvalid, syntax, type, constantValue); } - private ILambdaExpression CreateUnboundLambdaOperation(UnboundLambda unboundLambda) + private IOperation CreateUnboundLambdaOperation(UnboundLambda unboundLambda) { - bool isInvalid = unboundLambda.HasErrors; - SyntaxNode syntax = unboundLambda.Syntax; - // This matches the SemanticModel implementation. This is because in VB, lambdas by themselves - // do not have a type. To get the type of a lambda expression in the SemanticModel, you need to look at - // TypeInfo.ConvertedType, rather than TypeInfo.Type. We replicate that behavior here. To get the type of - // an IUnboundLambdaExpression, you need to look at the parent IConversionExpression. - ITypeSymbol type = null; - Optional constantValue = ConvertToOptional(unboundLambda.ConstantValue); - Lazy internalLambda = new Lazy(() => CreateBoundLambdaOperation(unboundLambda.BindForErrorRecovery())); - return new LazyLambdaFromUnboundLambdaExpression(internalLambda, isInvalid, syntax, type, constantValue); + // We want to ensure that we never see the UnboundLambda node, and that we don't end up having two different IOperation + // nodes for the lambda expression. So, we ask the semantic model for the IOperation node for the unbound lambda syntax. + // We are counting on the fact that will do the error recovery and actually create the BoundLambda node appropriate for + // this syntax node. + var lambdaOperation = _semanticModel.GetOperationInternal(unboundLambda.Syntax); + Debug.Assert(lambdaOperation.Kind == OperationKind.LambdaExpression); + return lambdaOperation; } private ILambdaExpression CreateBoundLambdaOperation(BoundLambda boundLambda) diff --git a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_ILambdaExpression.cs b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_ILambdaExpression.cs index 715f6e80305730b9062d3b62479306d0024a53c4..4355e7aa5f40cf518ea458c878cc4cc7e5f5ddb2 100644 --- a/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_ILambdaExpression.cs +++ b/src/Compilers/CSharp/Test/Semantic/IOperation/IOperationTests_ILambdaExpression.cs @@ -1,4 +1,6 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Semantics; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; @@ -112,5 +114,49 @@ static void F() VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); } + + [Fact] + public void ILambdaExpression_UnboundLambda_ReferenceEquality() + { + string source = @" +using System; + +class Program +{ + static void Main(string[] args) + { + var x /**/= () => F()/**/; + } + + static void F() + { + } +} +"; + + var compilation = CreateCompilationWithMscorlibAndSystemCore(source); + var syntaxTree = compilation.SyntaxTrees[0]; + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + var variableDeclaration = syntaxTree.GetRoot().DescendantNodes().OfType().Single(); + var lambdaSyntax = (LambdaExpressionSyntax)variableDeclaration.Variables.Single().Initializer.Value; + + var variableDeclarationOperation = (IVariableDeclarationStatement)semanticModel.GetOperationInternal(variableDeclaration); + var variableTreeLambdaOperation = (ILambdaExpression)variableDeclarationOperation.Declarations.Single().Initializer; + var lambdaOperation = (ILambdaExpression)semanticModel.GetOperationInternal(lambdaSyntax); + + // Assert that both ways of getting to the lambda (requesting the lambda directly, and requesting via the lambda syntax) + // return the same bound node. + Assert.Same(variableTreeLambdaOperation, lambdaOperation); + + var variableDeclarationOperationSecondRequest = (IVariableDeclarationStatement)semanticModel.GetOperationInternal(variableDeclaration); + var variableTreeLambdaOperationSecondRequest = (ILambdaExpression)variableDeclarationOperation.Declarations.Single().Initializer; + var lambdaOperationSecondRequest = (ILambdaExpression)semanticModel.GetOperationInternal(lambdaSyntax); + + // Assert that, when request the variable declaration or the lambda for a second time, there is no rebinding of the + // underlying UnboundLambda, and we get the same ILambdaExpression as before + Assert.Same(variableTreeLambdaOperation, variableTreeLambdaOperationSecondRequest); + Assert.Same(lambdaOperation, lambdaOperationSecondRequest); + } } }