diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 446dbe7c01b22..852fa793b7080 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -786,10 +786,18 @@ protected BoundLocalDeclaration BindVariableDeclaration( // might own nested scope. bool hasErrors = localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); - var containingMethod = this.ContainingMemberOrLambda as MethodSymbol; - if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None) + if (localSymbol.RefKind == RefKind.In) { - Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator); + var refKeyword = typeSyntax.GetFirstToken(); + diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refKeyword.GetLocation(), refKeyword.ToString()); + } + else + { + var containingMethod = this.ContainingMemberOrLambda as MethodSymbol; + if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None) + { + Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator); + } } EqualsValueClauseSyntax equalsClauseSyntax = declarator.Initializer; diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index a453209d9db67..d6f0691152b0e 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -6258,8 +6258,14 @@ private ScanTypeFlags ScanNonArrayType(ParseTypeMode mode, out SyntaxToken lastT { ScanTypeFlags result; - // in a ref local or ref return, we treat "ref" as part of the type - if (this.CurrentToken.Kind == SyntaxKind.RefKeyword) + // in a ref local or ref return, we treat "ref" and "readonly ref" as part of the type + if (this.CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword && + this.PeekToken(1).Kind == SyntaxKind.RefKeyword) + { + this.EatToken(); + this.EatToken(); + } + else if (this.CurrentToken.Kind == SyntaxKind.RefKeyword) { this.EatToken(); } @@ -8743,6 +8749,12 @@ private void ParseDeclarationModifiers(SyntaxListBuilder list) SyntaxKind k; while (IsDeclarationModifier(k = this.CurrentToken.ContextualKind) || IsAdditionalLocalFunctionModifier(k)) { + // "readonly ref" is not a modifier, in our syntax. + if (k == SyntaxKind.ReadOnlyKeyword && this.PeekToken(1).Kind == SyntaxKind.RefKeyword) + { + break; + } + SyntaxToken mod; if (k == SyntaxKind.AsyncKeyword) { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyRefReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyRefReturnTests.cs index ed7c7f3f5f22b..170f424644f0a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyRefReturnTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyRefReturnTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { [CompilerTrait(CompilerFeature.ReadonlyReferences)] - public class ReadonlyRefReturnTests : CompilingTestBase + public class CodeGenReadonlyRefReturnTests : CompilingTestBase { [Fact] public void RefReturnArrayAccess() @@ -29,7 +29,7 @@ static readonly ref int M() "; //PROTOTYPE(readonlyRefs): this should work for now because readonly is treated as regular ref - var comp = CompileAndVerify(text, parseOptions: TestOptions.Latest); + var comp = CompileAndVerify(text, parseOptions: TestOptions.Regular); comp.VerifyIL("Program.M()", @" { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs index 33fb372bf0522..bbcb806b4b349 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs @@ -2717,7 +2717,7 @@ public void CompilationWithReferenceDirectives_Errors() ", options: TestOptions.Script), SyntaxFactory.ParseSyntaxTree(@" #r ""System.Core"" -") +", TestOptions.Regular) }; var compilation = CreateCompilationWithMscorlib45( @@ -2800,7 +2800,7 @@ class C { void Foo() { Console.WriteLine(3); } } -") +", TestOptions.Regular) }; var compilation = CreateCompilationWithMscorlib45( diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/DelegateTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/DelegateTests.cs index 76402241d8c5d..78f24ff111c50 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/DelegateTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/DelegateTests.cs @@ -731,5 +731,20 @@ public void RefReturningDelegate() Assert.Equal(RefKind.Ref, d.DelegateInvokeMethod.RefKind); Assert.Equal(RefKind.Ref, ((MethodSymbol)d.GetMembers("EndInvoke").Single()).RefKind); } + + [Fact] + public void ReadonlyRefReturningDelegate() + { + var source = @"delegate readonly ref int D();"; + + var comp = CreateCompilationWithMscorlib45(source); + comp.VerifyDiagnostics(); + + var global = comp.GlobalNamespace; + var d = global.GetMembers("D")[0] as NamedTypeSymbol; + //PROTOTYPE(readonlyRefeences): this will work as regular RefKind.Ref for now since binding is NYI + Assert.Equal(RefKind.Ref, d.DelegateInvokeMethod.RefKind); + Assert.Equal(RefKind.Ref, ((MethodSymbol)d.GetMembers("EndInvoke").Single()).RefKind); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedMethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedMethodTests.cs index 6694a260cc612..c25eeb234fe8e 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedMethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedMethodTests.cs @@ -375,6 +375,18 @@ class C { int field = 0; public ref int M() => ref field; +}"); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ReadonlyRefReturningExpressionBodiedMethod() + { + var comp = CreateCompilationWithMscorlib45(@" +class C +{ + int field = 0; + public readonly ref int M() => ref field; }"); comp.VerifyDiagnostics(); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs index d4475bc237a43..a721ddbc76102 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs @@ -504,5 +504,28 @@ class C Assert.True(p.IsExpressionBodied); Assert.Equal(RefKind.Ref, p.GetMethod.RefKind); } + + [Fact] + public void ReadonlyRefReturningExpressionBodiedProperty() + { + var comp = CreateCompilationWithMscorlib45(@" +class C +{ + int field = 0; + public readonly ref int P => ref field; +}"); + comp.VerifyDiagnostics(); + + var global = comp.GlobalNamespace; + var c = global.GetTypeMember("C"); + + var p = c.GetMember("P"); + Assert.Null(p.SetMethod); + Assert.NotNull(p.GetMethod); + Assert.False(p.GetMethod.IsImplicitlyDeclared); + Assert.True(p.IsExpressionBodied); + //PROTOTYPE(readonlyRef): binding is currently NYI so it is "Ref" for now. + Assert.Equal(RefKind.Ref, p.GetMethod.RefKind); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index 753ae4e0faaaa..bfd1c4e46b827 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -2073,6 +2073,23 @@ static ref void M() { } ); } + [Fact] + public void ReadonlyRefReturningVoidMethod() + { + var source = @" +static class C +{ + static readonly ref void M() { } +} +"; + + CreateCompilationWithMscorlib45(source).VerifyDiagnostics( + // (4,25): error CS1547: Keyword 'void' cannot be used in this context + // static readonly ref void M() { } + Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(4, 25) + ); + } + [Fact] public void RefReturningVoidMethodNested() { @@ -2086,8 +2103,7 @@ ref void M() { } } "; - var parseOptions = TestOptions.Regular; - CreateCompilationWithMscorlib45(source, parseOptions: parseOptions).VerifyDiagnostics( + CreateCompilationWithMscorlib45(source).VerifyDiagnostics( // (6,13): error CS1547: Keyword 'void' cannot be used in this context // ref void M() { } Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(6, 13), @@ -2097,6 +2113,33 @@ ref void M() { } ); } + [Fact] + public void ReadonlyRefReturningVoidMethodNested() + { + var source = @" +static class C +{ + static void Main() + { + // valid + readonly ref int M1() {throw null;} + + // not valid + readonly ref void M2() {M1(); throw null;} + + M2(); + } +} +"; + + var parseOptions = TestOptions.Regular; + CreateCompilationWithMscorlib45(source).VerifyDiagnostics( + // (10,22): error CS1547: Keyword 'void' cannot be used in this context + // readonly ref void M2() {M1(); throw null;} + Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(10, 22) + ); + } + [Fact] public void RefReturningAsyncMethod() { @@ -2119,5 +2162,28 @@ static async ref int M() { } Diagnostic(ErrorCode.ERR_ReturnExpected, "M").WithArguments("C.M()").WithLocation(4, 26) ); } + + [Fact] + public void ReadonlyRefReturningAsyncMethod() + { + var source = @" +static class C +{ + static async readonly ref int M() { } +} +"; + + CreateCompilationWithMscorlib45(source).VerifyDiagnostics( + // (4,18): error CS1073: Unexpected token 'readonly' + // static async readonly ref int M() { } + Diagnostic(ErrorCode.ERR_UnexpectedToken, "readonly").WithArguments("readonly").WithLocation(4, 18), + // (4,35): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // static async readonly ref int M() { } + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "M").WithLocation(4, 35), + // (4,35): error CS0161: 'C.M()': not all code paths return a value + // static async readonly ref int M() { } + Diagnostic(ErrorCode.ERR_ReturnExpected, "M").WithArguments("C.M()").WithLocation(4, 35) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/CSharpCompilerSyntaxTest.csproj b/src/Compilers/CSharp/Test/Syntax/CSharpCompilerSyntaxTest.csproj index db83b276f52fe..28bd1e8805f37 100644 --- a/src/Compilers/CSharp/Test/Syntax/CSharpCompilerSyntaxTest.csproj +++ b/src/Compilers/CSharp/Test/Syntax/CSharpCompilerSyntaxTest.csproj @@ -138,7 +138,7 @@ - + diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs index 4e7ae5a72a349..1769ca5fc28f5 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs @@ -18,7 +18,7 @@ public DeclarationParsingTests(ITestOutputHelper output) : base(output) { } protected override SyntaxTree ParseTree(string text, CSharpParseOptions options) { - return SyntaxFactory.ParseSyntaxTree(text, options ?? TestOptions.Latest); + return SyntaxFactory.ParseSyntaxTree(text, options ?? TestOptions.Regular); } [Fact] @@ -1982,7 +1982,7 @@ public void TestDelegateWithRefReturnType() public void TestDelegateWithReadonlyRefReturnType() { var text = "delegate readonly ref a b();"; - var file = this.ParseFile(text, TestOptions.Latest); + var file = this.ParseFile(text, TestOptions.Regular); Assert.NotNull(file); Assert.Equal(1, file.Members.Count); @@ -2499,7 +2499,7 @@ public void TestClassMethodWithRefReturn() public void TestClassMethodWithReadonlyRefReturn() { var text = "class a { readonly ref b X() { } }"; - var file = this.ParseFile(text, TestOptions.Latest); + var file = this.ParseFile(text, TestOptions.Regular); Assert.NotNull(file); Assert.Equal(1, file.Members.Count); @@ -2573,7 +2573,7 @@ public void TestClassMethodWithRef() public void TestClassMethodWithReadonlyRef() { var text = "class a { readonly ref }"; - var file = this.ParseFile(text, parseOptions: TestOptions.Latest); + var file = this.ParseFile(text, parseOptions: TestOptions.Regular); Assert.NotNull(file); Assert.Equal(1, file.Members.Count); @@ -3947,7 +3947,7 @@ public void TestClassPropertyWithRefReturn() public void TestClassPropertyWithReadonlyRefReturn() { var text = "class a { readonly ref b c { get; set; } }"; - var file = this.ParseFile(text, TestOptions.Latest); + var file = this.ParseFile(text, TestOptions.Regular); Assert.NotNull(file); Assert.Equal(1, file.Members.Count); @@ -4890,7 +4890,7 @@ public void TestClassIndexerWithRefReturn() public void TestClassIndexerWithReadonlyRefReturn() { var text = "class a { readonly ref b this[c d] { get; set; } }"; - var file = this.ParseFile(text, TestOptions.Latest); + var file = this.ParseFile(text, TestOptions.Regular); Assert.NotNull(file); Assert.Equal(1, file.Members.Count); diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturns.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturnsTests.cs similarity index 66% rename from src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturns.cs rename to src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturnsTests.cs index b4e3f4367a77a..a80f8fac6b147 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturns.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ReadonlyRefReturnsTests.cs @@ -65,7 +65,6 @@ class Program { static void Main() { - readonly ref int local = ref (new int[1])[0]; } readonly ref int Field; @@ -86,36 +85,74 @@ static async readonly ref Task M() } "; - ParseAndValidate(text, TestOptions.Latest, - // (7,9): error CS0106: The modifier 'readonly' is not valid for this item - // readonly ref int local = ref (new int[1])[0]; - Diagnostic(ErrorCode.ERR_BadMemberFlag, "readonly").WithArguments("readonly").WithLocation(7, 9), - // (10,27): error CS1003: Syntax error, '(' expected + ParseAndValidate(text, TestOptions.Regular, + // (9,27): error CS1003: Syntax error, '(' expected // readonly ref int Field; - Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments("(", ";").WithLocation(10, 27), - // (10,27): error CS1026: ) expected + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments("(", ";").WithLocation(9, 27), + // (9,27): error CS1026: ) expected // readonly ref int Field; - Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(10, 27), - // (12,41): error CS1519: Invalid token 'operator' in class, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_CloseParenExpected, ";").WithLocation(9, 27), + // (11,41): error CS1519: Invalid token 'operator' in class, struct, or interface member declaration // public static readonly ref Program operator +(Program x, Program y) - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(12, 41), - // (12,41): error CS1519: Invalid token 'operator' in class, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(11, 41), + // (11,41): error CS1519: Invalid token 'operator' in class, struct, or interface member declaration // public static readonly ref Program operator +(Program x, Program y) - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(12, 41), - // (13,5): error CS1519: Invalid token '{' in class, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(11, 41), + // (12,5): error CS1519: Invalid token '{' in class, struct, or interface member declaration // { - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(13, 5), - // (13,5): error CS1519: Invalid token '{' in class, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(12, 5), + // (12,5): error CS1519: Invalid token '{' in class, struct, or interface member declaration // { - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(13, 5), - // (23,25): error CS1031: Type expected + Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(12, 5), + // (22,25): error CS1031: Type expected // public readonly ref virtual int* P1 => throw null; - Diagnostic(ErrorCode.ERR_TypeExpected, "virtual").WithLocation(23, 25), - // (25,1): error CS1022: Type or namespace definition, or end-of-file expected + Diagnostic(ErrorCode.ERR_TypeExpected, "virtual").WithLocation(22, 25), + // (24,1): error CS1022: Type or namespace definition, or end-of-file expected // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(25, 1) - + Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(24, 1) ); } + + [Fact] + public void ReadonlyRefReturn_UnexpectedBindTime() + { + var text = @" + +class Program +{ + static void Main() + { + readonly ref int local = ref (new int[1])[0]; + + (readonly ref int, readonly ref int Alice)? t = null; + + System.Collections.Generic.List x = null; + + Use(local); + Use(t); + Use(x); + } + + static void Use(T dummy) + { + } +} +"; + //PROTOTYPE(readonlyRef): binding now falls back on regular "Ref", otherwise there should be one more error on the local declaration + + var comp = CreateCompilationWithMscorlib45(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }); + comp.VerifyDiagnostics( + // (9,19): error CS1073: Unexpected token 'ref' + // (readonly ref int, readonly ref int Alice)? t = null; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(9, 19), + // (9,37): error CS1073: Unexpected token 'ref' + // (readonly ref int, readonly ref int Alice)? t = null; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(9, 37), + // (11,50): error CS1073: Unexpected token 'ref' + // System.Collections.Generic.List x = null; + Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(11, 50) + ); + } + } } diff --git a/src/Compilers/Test/Utilities/CSharp/TestOptions.cs b/src/Compilers/Test/Utilities/CSharp/TestOptions.cs index 081156b14a1b4..71a5738b21718 100644 --- a/src/Compilers/Test/Utilities/CSharp/TestOptions.cs +++ b/src/Compilers/Test/Utilities/CSharp/TestOptions.cs @@ -11,15 +11,13 @@ public static class TestOptions { // Disable documentation comments by default so that we don't need to // document every public member of every test input. - public static readonly CSharpParseOptions Script = new CSharpParseOptions(kind: SourceCodeKind.Script, documentationMode: DocumentationMode.None); - public static readonly CSharpParseOptions Regular = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None); + public static readonly CSharpParseOptions Regular = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None).WithLanguageVersion(LanguageVersion.Latest); + public static readonly CSharpParseOptions Script = Regular.WithKind(SourceCodeKind.Script); public static readonly CSharpParseOptions Regular6 = Regular.WithLanguageVersion(LanguageVersion.CSharp6); - public static readonly CSharpParseOptions RegularWithDocumentationComments = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Diagnose); - - public static readonly CSharpParseOptions Latest = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None).WithLanguageVersion(LanguageVersion.Latest); + public static readonly CSharpParseOptions RegularWithDocumentationComments = Regular.WithDocumentationMode(DocumentationMode.Diagnose); private static readonly SmallDictionary s_experimentalFeatures = new SmallDictionary { }; - public static readonly CSharpParseOptions ExperimentalParseOptions = Latest.WithFeatures(s_experimentalFeatures); + public static readonly CSharpParseOptions ExperimentalParseOptions = Regular.WithFeatures(s_experimentalFeatures); // Enable pattern-switch translation even for switches that use no new syntax. This is used // to help ensure compatibility of the semantics of the new switch binder with the old switch