File: Semantics\LambdaDiscardParametersTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Semantic\Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests)
// 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.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    [CompilerTrait(CompilerFeature.LambdaDiscardParameters)]
    public class LambdaDiscardParametersTests : CompilingTestBase
    {
        [Fact]
        public void DiscardParameters_CSharp8()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, string, long> f1 = (_, _) => 3L;
        System.Console.WriteLine(f1(1, null));
 
        System.Func<int, int, int, long> f2 = (a, _,
            _) => 4L;
 
        System.Func<int, int, int, long> f3 = (_, a,
            _) => 5L;
 
        System.Func<int, int, int, long> f4 = (_,
            _,
            _) => 6L;
 
        System.Func<int, int, int, long> f5 = (_,
            _,
            a) => 7L;
    }
}", parseOptions: TestOptions.Regular8);
 
            comp.VerifyDiagnostics(
                // (6,51): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //         System.Func<short, string, long> f1 = (_, _) => 3L;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(6, 51),
                // (10,13): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //             _) => 4L;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(10, 13),
                // (13,13): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //             _) => 5L;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(13, 13),
                // (16,13): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //             _,
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(16, 13),
                // (17,13): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //             _) => 6L;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(17, 13),
                // (20,13): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //             _,
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(20, 13)
                );
 
            var tree = comp.SyntaxTrees.Single();
            var underscores = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Where(p => p.Identifier.ToString() == "_").ToArray();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
            VerifyDiscardParameterSymbol(underscores[0], "System.Int16", CodeAnalysis.NullableAnnotation.NotAnnotated, model);
            VerifyDiscardParameterSymbol(underscores[1], "System.String", CodeAnalysis.NullableAnnotation.None, model);
        }
 
        [Fact]
        public void DiscardParameters_LocalFunctions()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        long f1(short _, string _) => 3L;
        System.Console.WriteLine(f1(1, null));
    }
}", parseOptions: TestOptions.Regular9);
 
            comp.VerifyDiagnostics(
                // (6,33): error CS0100: The parameter name '_' is a duplicate
                //         long f1(short _, string _) => 3L;
                Diagnostic(ErrorCode.ERR_DuplicateParamName, "_").WithArguments("_").WithLocation(6, 33)
                );
        }
 
        [Fact]
        public void DiscardParameters_Methods()
        {
            var comp = CreateCompilation(@"
public class C
{
    public long M(short _, string _) => 3L;
}", parseOptions: TestOptions.Regular9);
 
            comp.VerifyDiagnostics(
                // (4,35): error CS0100: The parameter name '_' is a duplicate
                //     public long M(short _, string _) => 3L;
                Diagnostic(ErrorCode.ERR_DuplicateParamName, "_").WithArguments("_").WithLocation(4, 35)
                );
        }
 
        private static void VerifyDiscardParameterSymbol(ParameterSyntax underscore, string expectedType, CodeAnalysis.NullableAnnotation expectedAnnotation, SemanticModel model)
        {
            Assert.Null(model.GetSymbolInfo(underscore).Symbol);
            var symbol1 = model.GetDeclaredSymbol(underscore);
            Assert.Equal(expectedType, symbol1.Type.ToTestDisplayString());
            Assert.Equal("_", symbol1.Name);
            Assert.True(symbol1.IsDiscard);
            Assert.Equal(expectedType, symbol1.Type.ToTestDisplayString());
            Assert.Equal(expectedAnnotation, symbol1.NullableAnnotation);
        }
 
        [Fact]
        public void DiscardParameters()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, short, long> f1 = (_, _) => { long _ = 3; return _; };
        System.Console.Write(f1(0, 0));
 
        System.Func<short, short, int, long> f2 = (_, _, a) => 4L + a;
        System.Console.Write(f2(0, 0, 1));
 
        System.Func<int, short, short, long> f3 = (a, _, _) => 5L + a;
        System.Console.Write(f3(1, 0, 0));
    }
}", options: TestOptions.DebugExe);
 
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "356");
        }
 
        [Fact]
        public void DiscardParameters_RefAndOut()
        {
            var comp = CreateCompilation(@"
class C
{
    delegate int RefAndOut(ref int i, out int j);
    static void M()
    {
        RefAndOut f1 = (ref int _, out int _) =>
            {
                return 2;
            };
    }
}");
 
            comp.VerifyDiagnostics(
                // (9,17): error CS0177: The out parameter '_' must be assigned to before control leaves the current method
                //                 return 2;
                Diagnostic(ErrorCode.ERR_ParamUnassigned, "return 2;").WithArguments("_").WithLocation(9, 17)
                );
        }
 
        [Fact]
        public void DiscardParameters_OnLocalFunction()
        {
            var comp = CreateCompilation(@"
class C
{
    static void M()
    {
        local(1, 2);
        void local(int _, int _) { }
    }
}");
 
            comp.VerifyDiagnostics(
                // (7,31): error CS0100: The parameter name '_' is a duplicate
                //         void local(int _, int _) { }
                Diagnostic(ErrorCode.ERR_DuplicateParamName, "_").WithArguments("_").WithLocation(7, 31)
                );
        }
 
        [Fact]
        public void DiscardParameters_UnicodeUnderscore()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, short, long> f1 = (\u005f, \u005f) => 3L;
        \u005f = 1;
    }
}");
            comp.VerifyDiagnostics(
                // (6,55): error CS0100: The parameter name '_' is a duplicate
                //         System.Func<short, short, long> f1 = (\u005f, \u005f) => 3L;
                Diagnostic(ErrorCode.ERR_DuplicateParamName, @"\u005f").WithArguments("_").WithLocation(6, 55),
                // (7,9): error CS0103: The name '_' does not exist in the current context
                //         \u005f = 1;
                Diagnostic(ErrorCode.ERR_NameNotInContext, @"\u005f").WithArguments("_").WithLocation(7, 9)
                );
        }
 
        [Fact]
        public void DiscardParameters_EscapedUnderscore()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, short, long> f1 = (@_, @_) => 3L;
        @_ = 1;
    }
}");
            comp.VerifyDiagnostics(
                // (6,51): error CS0100: The parameter name '_' is a duplicate
                //         System.Func<short, short, long> f1 = (@_, @_) => 3L;
                Diagnostic(ErrorCode.ERR_DuplicateParamName, "@_").WithArguments("_").WithLocation(6, 51),
                // (7,9): error CS0103: The name '_' does not exist in the current context
                //         @_ = 1;
                Diagnostic(ErrorCode.ERR_NameNotInContext, "@_").WithArguments("_").WithLocation(7, 9)
                );
        }
 
        [Fact]
        public void DiscardParameters_SingleUnderscoreParameter()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, short, long> f1 = (_, a) =>
        {
            int _ = 0; // 1
            return _;
        };
    }
}");
            comp.VerifyDiagnostics(
                // (8,17): error CS0136: A local or parameter named '_' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
                //             int _ = 0; // 1
                Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "_").WithArguments("_").WithLocation(8, 17)
                );
        }
 
        [Fact]
        public void DiscardParameters_SingleUnderscoreParameter_InScopeWithUnderscoreLocal()
        {
            var src = @"
public class C
{
    public static int M()
    {
        int _ = 0;
        System.Func<short, short, long> f1 = (_, a) => 0;
        System.Func<short, short, long> f2 = (_, _) => 0;
        return _;
    }
}";
            var comp = CreateCompilation(src, parseOptions: TestOptions.Regular7_3);
            comp.VerifyDiagnostics(
                // (7,47): error CS0136: A local or parameter named '_' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
                //         System.Func<short, short, long> f1 = (_, a) => 0;
                Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "_").WithArguments("_").WithLocation(7, 47),
                // (8,50): error CS8370: Feature 'lambda discard parameters' is not available in C# 7.3. Please use language version 9.0 or greater.
                //         System.Func<short, short, long> f2 = (_, _) => 0;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(8, 50)
                );
 
            var comp2 = CreateCompilation(src, parseOptions: TestOptions.Regular8);
            comp2.VerifyDiagnostics(
                // (8,50): error CS8400: Feature 'lambda discard parameters' is not available in C# 8.0. Please use language version 9.0 or greater.
                //         System.Func<short, short, long> f2 = (_, _) => 0;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion8, "_").WithArguments("lambda discard parameters", "9.0").WithLocation(8, 50)
                );
 
            var comp3 = CreateCompilation(src, parseOptions: TestOptions.Regular9);
            comp3.VerifyDiagnostics();
        }
 
        [Fact]
        public void DiscardParameters_WithTypes()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<short, short, long> f1 = (short _, short _) => 3L;
        System.Console.Write(f1(0, 0));
 
        System.Func<short, short, int, long> f2 = (short _, short _, int a) => 4L + a;
        System.Console.Write(f2(0, 0, 1));
 
        System.Func<int, short, short, long> f3 = (int a, short _, short _) => 5L + a;
        System.Console.Write(f3(1, 0, 0));
    }
}", options: TestOptions.DebugExe);
 
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "356");
        }
 
        [Fact]
        public void DiscardParameters_InDelegates()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<int, int, long> f1 = delegate(int _, int _) { return 3L; };
        System.Console.Write(f1(0, 0));
 
        System.Func<int, int, int, long> f2 = delegate(int _, int _, int a) { return 4L + a; };
        System.Console.Write(f2(0, 0, 1));
    }
}", options: TestOptions.DebugExe);
 
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "35");
        }
 
        [Fact]
        public void DiscardParameters_InDelegates_WithAttribute()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<int, int, long> f1 = delegate([System.Obsolete]int _, int _ = 0) { return 3L; };
    }
}");
 
            comp.VerifyDiagnostics(
                // (6,51): error CS7014: Attributes are not valid in this context.
                //         System.Func<int, int, long> f1 = delegate([System.Obsolete]int _, int _ = 0) { return 3L; };
                Diagnostic(ErrorCode.ERR_AttributesNotAllowed, "[System.Obsolete]").WithLocation(6, 51),
                // (6,81): error CS1065: Default values are not valid in this context.
                //         System.Func<int, int, long> f1 = delegate([System.Obsolete]int _, int _ = 0) { return 3L; };
                Diagnostic(ErrorCode.ERR_DefaultValueNotAllowed, "=").WithLocation(6, 81));
        }
 
        [Fact]
        public void DiscardParameters_WithAttribute()
        {
            var source =
@"using System;
class AAttribute : Attribute { }
class C
{
    static void Main()
    {
        Action<object, object> a;
        a = ([A] _, y) => { };
        a = (object x, [A] object _) => { };
    }
}";
 
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
            comp.VerifyDiagnostics(
                // (8,14): error CS8773: Feature 'lambda attributes' is not available in C# 9.0. Please use language version 10.0 or greater.
                //         a = ([A] _, y) => { };
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "[A]").WithArguments("lambda attributes", "10.0").WithLocation(8, 14),
                // (9,24): error CS8773: Feature 'lambda attributes' is not available in C# 9.0. Please use language version 10.0 or greater.
                //         a = (object x, [A] object _) => { };
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "[A]").WithArguments("lambda attributes", "10.0").WithLocation(9, 24));
            verifyAttributes(comp);
 
            comp = CreateCompilation(source, parseOptions: TestOptions.Regular10);
            comp.VerifyDiagnostics();
            verifyAttributes(comp);
 
            static void verifyAttributes(CSharpCompilation comp)
            {
                var tree = comp.SyntaxTrees[0];
                var model = comp.GetSemanticModel(tree);
                var exprs = tree.GetRoot().DescendantNodes().OfType<LambdaExpressionSyntax>();
                var lambdas = exprs.Select(e => (IMethodSymbol)model.GetSymbolInfo(e).Symbol).ToArray();
                Assert.Equal(2, lambdas.Length);
                Assert.Equal(new[] { "AAttribute" }, getParameterAttributes(lambdas[0].Parameters[0]));
                Assert.Equal(new string[0], getParameterAttributes(lambdas[0].Parameters[1]));
                Assert.Equal(new string[0], getParameterAttributes(lambdas[1].Parameters[0]));
                Assert.Equal(new[] { "AAttribute" }, getParameterAttributes(lambdas[1].Parameters[1]));
            }
 
            static ImmutableArray<string> getParameterAttributes(IParameterSymbol parameter) => parameter.GetAttributes().SelectAsArray(a => a.ToString());
        }
 
        [Fact]
        public void DiscardParameters_NotInScope()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<int, short, int> f = (_, _) => _;
    }
}");
 
            comp.VerifyDiagnostics(
                // (6,52): error CS0103: The name '_' does not exist in the current context
                //         System.Func<int, short, int> f = (_, _) => _;
                Diagnostic(ErrorCode.ERR_NameNotInContext, "_").WithArguments("_").WithLocation(6, 52)
                );
 
            var tree = comp.SyntaxTrees.Single();
            var underscoreParameters = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Where(p => p.ToString() == "_").ToArray();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
            VerifyDiscardParameterSymbol(underscoreParameters[0], "System.Int32", CodeAnalysis.NullableAnnotation.NotAnnotated, model);
            VerifyDiscardParameterSymbol(underscoreParameters[1], "System.Int16", CodeAnalysis.NullableAnnotation.NotAnnotated, model);
 
            var underscore = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(p => p.ToString() == "_").Single();
            Assert.Null(model.GetSymbolInfo(underscore).Symbol);
        }
 
        [Fact]
        public void DiscardParameters_NotInScope_BindToOutsideLocal()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        int _ = 42;
        System.Func<string, string, int> f = (_, _) => ++_;
        System.Func<long, string, long> f2 = (_, a) => ++_;
        System.Console.Write(f(null, null) + "" "");
        System.Console.Write(f2(1, null) + "" "");
        System.Console.Write(_);
    }
}", options: TestOptions.DebugExe);
            // Note that naming one of the parameters seems irrelevant but results in a binding change
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "43 2 43");
 
            var tree = comp.SyntaxTrees.Single();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
            var underscores = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(p => p.ToString() == "_").ToArray();
            Assert.Equal(3, underscores.Length);
 
            var localSymbol = model.GetSymbolInfo(underscores[0]).Symbol;
            Assert.Equal("System.Int32 _", localSymbol.ToTestDisplayString());
            Assert.Equal(SymbolKind.Local, localSymbol.Kind);
 
            var parameterSymbol = model.GetSymbolInfo(underscores[1]).Symbol;
            Assert.Equal("System.Int64 _", parameterSymbol.ToTestDisplayString());
            Assert.Equal(SymbolKind.Parameter, parameterSymbol.Kind);
        }
 
        [Fact]
        public void DiscardParameters_NotInScope_BindToOutsideLocal_Nested()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        int _ = 42;
        System.Func<string, string, int> f = (_, _) =>
        {
            System.Func<string, string, int> f2 = (_, _) => ++_;
            return f2(null, null);
        };
        System.Console.Write(f(null, null));
    }
}", options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "43");
 
            var tree = comp.SyntaxTrees.Single();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
            var underscore = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(p => p.ToString() == "_").Single();
 
            var localSymbol = model.GetSymbolInfo(underscore).Symbol;
            Assert.Equal("System.Int32 _", localSymbol.ToTestDisplayString());
            Assert.Equal(SymbolKind.Local, localSymbol.Kind);
        }
 
        [Fact]
        public void DiscardParameters_NotInScope_DeclareLocalNamedUnderscoreInside()
        {
            var comp = CreateCompilation(@"
class C
{
    static void M()
    {
        System.Func<string, string, long> f = (_, _) => { long _ = 0; return _++; };
        System.Func<string, string, long> f2 = (_, a) => {
            long _ = 0; // 1
            return _++;
        };
    }
}");
            // Note that naming one of the parameters seems irrelevant but results in a binding change
            comp.VerifyDiagnostics(
                // (8,18): error CS0136: A local or parameter named '_' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
                //             long _ = 0; // 1
                Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "_").WithArguments("_").WithLocation(8, 18)
                );
 
            var tree = comp.SyntaxTrees.Single();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
            var underscores = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(p => p.ToString() == "_").ToArray();
            Assert.Equal(2, underscores.Length);
 
            var localSymbol = model.GetSymbolInfo(underscores[0]).Symbol;
            Assert.Equal("System.Int64 _", localSymbol.ToTestDisplayString());
            Assert.Equal(SymbolKind.Local, localSymbol.Kind);
 
            var parameterSymbol = model.GetSymbolInfo(underscores[1]).Symbol;
            Assert.Equal("System.Int64 _", parameterSymbol.ToTestDisplayString());
            Assert.Equal(SymbolKind.Local, parameterSymbol.Kind);
        }
 
        [Fact]
        public void DiscardParameters_NotInScope_Nameof()
        {
            var comp = CreateCompilation(@"
class C
{
    static void M()
    {
        System.Func<string, string, string> f = (_, _) => nameof(_); // 1
        System.Func<long, string, string> f2 = (_, a) => nameof(_);
        System.Func<long, string> f3 = (_) => nameof(_);
    }
}");
            // Note that naming one of the parameters seems irrelevant but results in a binding change
            comp.VerifyDiagnostics(
                // (6,66): error CS0103: The name '_' does not exist in the current context
                //         System.Func<string, string, string> f = (_, _) => nameof(_); // 1
                Diagnostic(ErrorCode.ERR_NameNotInContext, "_").WithArguments("_").WithLocation(6, 66)
                );
        }
 
        [Fact]
        public void DiscardParameters_NotADiscardWhenSingleUnderscore()
        {
            var comp = CreateCompilation(@"
public class C
{
    public static void Main()
    {
        System.Func<int, int, int> f = (a, _) => _;
        System.Console.Write(f(1, 2));
 
        System.Func<int, int, int> g = (_, a) => _;
        System.Console.Write(g(1, 2));
    }
}", options: TestOptions.DebugExe);
 
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "21");
 
            var tree = comp.SyntaxTrees.Single();
            var underscoreParameters = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Where(p => p.ToString() == "_").ToArray();
            var model = comp.GetSemanticModel(tree, ignoreAccessibility: false);
 
            var parameterSymbol1 = model.GetDeclaredSymbol(underscoreParameters[0]);
            Assert.NotNull(parameterSymbol1);
            Assert.False(parameterSymbol1.IsDiscard);
 
            var parameterSymbol2 = model.GetDeclaredSymbol(underscoreParameters[1]);
            Assert.NotNull(parameterSymbol2);
            Assert.False(parameterSymbol2.IsDiscard);
        }
 
        [Fact]
        public void DiscardParameters_Shadowing()
        {
            var comp = CreateCompilation(@"
using System;
public class C
{
    public static void M()
    {
        Action<int> f1 = (_) =>
        {
            _.ToString(); // ok
            Action<int> g2 = (_) => _.ToString(); // ok
        };
 
        Action<int, int> f2 = (_, _) =>
        {
            _.ToString(); // error
            Action<int> g2 = (_) => _.ToString(); // ok
        };
    }
}");
 
            comp.VerifyDiagnostics(
                // (15,13): error CS0103: The name '_' does not exist in the current context
                //             _.ToString(); // error
                Diagnostic(ErrorCode.ERR_NameNotInContext, "_").WithArguments("_").WithLocation(15, 13)
                );
        }
    }
}