// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public partial class IOperationTests : SemanticModelTestBase
{
[Fact]
public void TestLocalFunction_ContainingMethodParameterReference()
{
string source = @"
class C
{
public void M(int x)
{
/**/int Local(int p1)
{
return x++;
}/**/
Local(0);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local(System.Int32 p1)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local(i ... }')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'return x++;')
ReturnedValue: IIncrementExpression (UnaryOperandKind.IntegerPostfixIncrement) (OperationKind.IncrementExpression, Type: System.Int32) (Syntax: 'x++')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_ContainingMethodParameterReference_ExpressionBodied()
{
string source = @"
class C
{
public void M(int x)
{
/**/int Local(int p1) => x++;/**/
Local(0);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local(System.Int32 p1)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local(i ... p1) => x++;')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> x++')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'x++')
ReturnedValue: IIncrementExpression (UnaryOperandKind.IntegerPostfixIncrement) (OperationKind.IncrementExpression, Type: System.Int32) (Syntax: 'x++')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_LocalFunctionParameterReference()
{
string source = @"
class C
{
public void M()
{
/**/int Local(int x) => x++;/**/
Local(0);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local(System.Int32 x)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local(int x) => x++;')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> x++')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'x++')
ReturnedValue: IIncrementExpression (UnaryOperandKind.IntegerPostfixIncrement) (OperationKind.IncrementExpression, Type: System.Int32) (Syntax: 'x++')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_ContainingLocalFunctionParameterReference()
{
string source = @"
class C
{
public void M()
{
int LocalOuter (int x)
{
/**/int Local(int y) => x + y;/**/
return Local(x);
}
LocalOuter(0);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local(System.Int32 y)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local(i ... ) => x + y;')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> x + y')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'x + y')
ReturnedValue: IBinaryOperatorExpression (BinaryOperationKind.IntegerAdd) (OperationKind.BinaryOperatorExpression, Type: System.Int32) (Syntax: 'x + y')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
Right: IParameterReferenceExpression: y (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'y')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_LocalFunctionReference()
{
string source = @"
class C
{
public void M()
{
int x;
int Local(int p1) => x++;
int Local2(int p1) => Local(p1);
/**/int Local3(int p1) => x + Local2(p1);/**/
Local3(x = 0);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local3(System.Int32 p1)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local3( ... Local2(p1);')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> x + Local2(p1)')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'x + Local2(p1)')
ReturnedValue: IBinaryOperatorExpression (BinaryOperationKind.IntegerAdd) (OperationKind.BinaryOperatorExpression, Type: System.Int32) (Syntax: 'x + Local2(p1)')
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: System.Int32) (Syntax: 'x')
Right: IInvocationExpression (System.Int32 Local2(System.Int32 p1)) (OperationKind.InvocationExpression, Type: System.Int32) (Syntax: 'Local2(p1)')
Instance Receiver: null
Arguments(1):
IArgument (ArgumentKind.Explicit, Matching Parameter: p1) (OperationKind.Argument) (Syntax: 'p1')
IParameterReferenceExpression: p1 (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p1')
InConversion: null
OutConversion: null
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_Recursion()
{
string source = @"
class C
{
public void M(int x)
{
/**/int Local(int p1) => Local(x + p1);/**/
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local(System.Int32 p1)) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local(i ... al(x + p1);')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> Local(x + p1)')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'Local(x + p1)')
ReturnedValue: IInvocationExpression (System.Int32 Local(System.Int32 p1)) (OperationKind.InvocationExpression, Type: System.Int32) (Syntax: 'Local(x + p1)')
Instance Receiver: null
Arguments(1):
IArgument (ArgumentKind.Explicit, Matching Parameter: p1) (OperationKind.Argument) (Syntax: 'x + p1')
IBinaryOperatorExpression (BinaryOperationKind.IntegerAdd) (OperationKind.BinaryOperatorExpression, Type: System.Int32) (Syntax: 'x + p1')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
Right: IParameterReferenceExpression: p1 (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p1')
InConversion: null
OutConversion: null
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_Async()
{
string source = @"
using System.Threading.Tasks;
class C
{
public void M(int x)
{
/**/async Task LocalAsync(int p1)
{
await Task.Delay(0);
return x + p1;
}/**/
LocalAsync(0).Wait();
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Threading.Tasks.Task LocalAsync(System.Int32 p1)) (OperationKind.LocalFunctionStatement) (Syntax: 'async Task< ... }')
IBlockStatement (2 statements) (OperationKind.BlockStatement) (Syntax: '{ ... }')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: 'await Task.Delay(0);')
Expression: IAwaitExpression (OperationKind.AwaitExpression, Type: System.Void) (Syntax: 'await Task.Delay(0)')
AwaitedValue: IInvocationExpression (System.Threading.Tasks.Task System.Threading.Tasks.Task.Delay(System.Int32 millisecondsDelay)) (OperationKind.InvocationExpression, Type: System.Threading.Tasks.Task) (Syntax: 'Task.Delay(0)')
Instance Receiver: null
Arguments(1):
IArgument (ArgumentKind.Explicit, Matching Parameter: millisecondsDelay) (OperationKind.Argument) (Syntax: '0')
ILiteralExpression (Text: 0) (OperationKind.LiteralExpression, Type: System.Int32, Constant: 0) (Syntax: '0')
InConversion: null
OutConversion: null
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'return x + p1;')
ReturnedValue: IBinaryOperatorExpression (BinaryOperationKind.IntegerAdd) (OperationKind.BinaryOperatorExpression, Type: System.Int32) (Syntax: 'x + p1')
Left: IParameterReferenceExpression: x (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'x')
Right: IParameterReferenceExpression: p1 (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p1')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, additionalReferences: new[] { MscorlibRef_v46 });
}
[Fact]
public void TestLocalFunction_CaptureForEachVar()
{
string source = @"
class C
{
public void M(int[] array)
{
foreach (var x in array)
{
/**/int Local() => x;/**/
Local();
}
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: System.Int32 Local()) (OperationKind.LocalFunctionStatement) (Syntax: 'int Local() => x;')
IBlockStatement (1 statements) (OperationKind.BlockStatement) (Syntax: '=> x')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: 'x')
ReturnedValue: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: System.Int32) (Syntax: 'x')
";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_UseOfUnusedVar()
{
string source = @"
class C
{
void M()
{
F();
int x = 0;
/**/void F() => x++;/**/
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: void F()) (OperationKind.LocalFunctionStatement) (Syntax: 'void F() => x++;')
IBlockStatement (2 statements) (OperationKind.BlockStatement) (Syntax: '=> x++')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: 'x++')
Expression: IIncrementExpression (UnaryOperandKind.IntegerPostfixIncrement) (OperationKind.IncrementExpression, Type: System.Int32) (Syntax: 'x++')
Left: ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: System.Int32) (Syntax: 'x')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: '=> x++')
ReturnedValue: null
";
var expectedDiagnostics = new DiagnosticDescription[] {
// CS0165: Use of unassigned local variable 'x'
// F();
Diagnostic(ErrorCode.ERR_UseDefViolation, "F()").WithArguments("x").WithLocation(6, 9)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestLocalFunction_OutVar()
{
string source = @"
class C
{
void M(int p)
{
int x;
/**/void F(out int y) => y = p;/**/
F(out x);
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: void F(out System.Int32 y)) (OperationKind.LocalFunctionStatement) (Syntax: 'void F(out ... ) => y = p;')
IBlockStatement (2 statements) (OperationKind.BlockStatement) (Syntax: '=> y = p')
IExpressionStatement (OperationKind.ExpressionStatement) (Syntax: 'y = p')
Expression: ISimpleAssignmentExpression (OperationKind.SimpleAssignmentExpression, Type: System.Int32) (Syntax: 'y = p')
Left: IParameterReferenceExpression: y (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'y')
Right: IParameterReferenceExpression: p (OperationKind.ParameterReferenceExpression, Type: System.Int32) (Syntax: 'p')
IReturnStatement (OperationKind.ReturnStatement) (Syntax: '=> y = p')
ReturnedValue: null";
var expectedDiagnostics = DiagnosticDescription.None;
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestInvalidLocalFunction_MissingBody()
{
string source = @"
class C
{
void M(int p)
{
/**/void F(out int y) => ;/**/
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: void F(out System.Int32 y)) (OperationKind.LocalFunctionStatement, IsInvalid) (Syntax: 'void F(out int y) => ;')
IBlockStatement (2 statements) (OperationKind.BlockStatement, IsInvalid) (Syntax: '=> ')
IExpressionStatement (OperationKind.ExpressionStatement, IsInvalid) (Syntax: '')
Expression: IInvalidExpression (OperationKind.InvalidExpression, Type: ?, IsInvalid) (Syntax: '')
Children(0)
IReturnStatement (OperationKind.ReturnStatement, IsInvalid) (Syntax: '=> ')
ReturnedValue: null
";
var expectedDiagnostics = new DiagnosticDescription[] {
// file.cs(6,40): error CS1525: Invalid expression term ';'
// /**/void F(out int y) => ;/**/
Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(6, 40),
// file.cs(6,24): error CS0177: The out parameter 'y' must be assigned to before control leaves the current method
// /**/void F(out int y) => ;/**/
Diagnostic(ErrorCode.ERR_ParamUnassigned, "F").WithArguments("y").WithLocation(6, 24),
// file.cs(6,24): warning CS8321: The local function 'F' is declared but never used
// /**/void F(out int y) => ;/**/
Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "F").WithArguments("F").WithLocation(6, 24)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestInvalidLocalFunction_MissingParameters()
{
string source = @"
class C
{
void M(int p)
{
/**/void F( { }/**/;
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: void F()) (OperationKind.LocalFunctionStatement, IsInvalid) (Syntax: 'void F( { }')
IBlockStatement (1 statements) (OperationKind.BlockStatement, IsInvalid) (Syntax: '{ }')
IReturnStatement (OperationKind.ReturnStatement, IsInvalid) (Syntax: '{ }')
ReturnedValue: null
";
var expectedDiagnostics = new DiagnosticDescription[] {
// file.cs(6,27): error CS1026: ) expected
// /**/void F( { }/**/;
Diagnostic(ErrorCode.ERR_CloseParenExpected, "{").WithLocation(6, 27),
// file.cs(6,24): warning CS8321: The local function 'F' is declared but never used
// /**/void F( { }/**/;
Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "F").WithArguments("F").WithLocation(6, 24)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
[Fact]
public void TestInvalidLocalFunction_InvalidReturnType()
{
string source = @"
class C
{
void M(int p)
{
/**/X F() { }/**/;
}
}
";
string expectedOperationTree = @"
ILocalFunctionStatement (Local Function: X F()) (OperationKind.LocalFunctionStatement, IsInvalid) (Syntax: 'X F() { }')
IBlockStatement (0 statements) (OperationKind.BlockStatement) (Syntax: '{ }')
";
var expectedDiagnostics = new DiagnosticDescription[] {
// CS0161: 'F()': not all code paths return a value
// /**/X F() { }/**/;
Diagnostic(ErrorCode.ERR_ReturnExpected, "F").WithArguments("F()").WithLocation(6, 21),
// CS0246: The type or namespace name 'X' could not be found (are you missing a using directive or an assembly reference?)
// /**/X F() { }/**/;
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "X").WithArguments("X").WithLocation(6, 19),
// CS8321: The local function 'F' is declared but never used
// /**/X F() { }/**/;
Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "F").WithArguments("F").WithLocation(6, 21)
};
VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics);
}
}
}