-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Parse partial events and constructors #76860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
ba8ca9d
941b824
ced9913
504f58d
b541347
12a3225
a339c79
c89acca
02af237
4c61a35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1634,24 +1634,25 @@ private bool IsPartialType() | |
|
||
private bool IsPartialMember() | ||
{ | ||
// note(cyrusn): this could have been written like so: | ||
// | ||
// return | ||
// this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword && | ||
// this.PeekToken(1).Kind == SyntaxKind.VoidKeyword; | ||
// | ||
// However, we want to be lenient and allow the user to write | ||
// 'partial' in most modifier lists. We will then provide them with | ||
// a more specific message later in binding that they are doing | ||
// something wrong. | ||
// | ||
// Some might argue that the simple check would suffice. | ||
// However, we'd like to maintain behavior with | ||
// previously shipped versions, and so we're keeping this code. | ||
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword); | ||
|
||
// Here we check for: | ||
// Check for: | ||
// partial event | ||
if (this.PeekToken(1).Kind == SyntaxKind.EventKeyword) | ||
{ | ||
return IsFeatureEnabled(MessageID.IDS_FeaturePartialEventsAndConstructors); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not to belabor it, but, it wasn't obvious to me why event parsing should be conditional on LangVersion. Maybe it's simpler to just be consistent? Perhaps there is some existing form we are trying to avoid breaking? Feel free to add as an open question for a future PR, if any further investigation/work is found to be needed here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I did this for consistency but now I realize in parsing we want to avoid being conditional like this whenever possible. I don't think this can break anyone since |
||
} | ||
|
||
// Check for constructor: | ||
// partial Identifier( | ||
if (this.PeekToken(1).Kind == SyntaxKind.IdentifierToken && | ||
this.PeekToken(2).Kind == SyntaxKind.OpenParenToken) | ||
{ | ||
return IsFeatureEnabled(MessageID.IDS_FeaturePartialEventsAndConstructors); | ||
} | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Check for method/property: | ||
// partial ReturnType MemberName | ||
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword); | ||
using var _ = this.GetDisposableResetPoint(resetOnDispose: true); | ||
|
||
this.EatToken(); // partial | ||
|
@@ -5680,7 +5681,7 @@ private bool IsTrueIdentifier() | |
{ | ||
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken) | ||
{ | ||
if (!IsCurrentTokenPartialKeywordOfPartialMethodOrType() && | ||
if (!IsCurrentTokenPartialKeywordOfPartialMemberOrType() && | ||
!IsCurrentTokenQueryKeywordInQuery() && | ||
!IsCurrentTokenWhereOfConstraintClause()) | ||
{ | ||
|
@@ -5727,7 +5728,7 @@ private SyntaxToken ParseIdentifierToken(ErrorCode code = ErrorCode.ERR_Identifi | |
// show the correct parameter help in this case. So, when we see "partial" we check if it's being used | ||
// as an identifier or as a contextual keyword. If it's the latter then we bail out. See | ||
// Bug: vswhidbey/542125 | ||
if (IsCurrentTokenPartialKeywordOfPartialMethodOrType() || IsCurrentTokenQueryKeywordInQuery()) | ||
if (IsCurrentTokenPartialKeywordOfPartialMemberOrType() || IsCurrentTokenQueryKeywordInQuery()) | ||
{ | ||
var result = CreateMissingIdentifierToken(); | ||
result = this.AddError(result, ErrorCode.ERR_InvalidExprTerm, this.CurrentToken.Text); | ||
|
@@ -5754,7 +5755,7 @@ private bool IsCurrentTokenQueryKeywordInQuery() | |
return this.IsInQuery && this.IsCurrentTokenQueryContextualKeyword; | ||
} | ||
|
||
private bool IsCurrentTokenPartialKeywordOfPartialMethodOrType() | ||
private bool IsCurrentTokenPartialKeywordOfPartialMemberOrType() | ||
{ | ||
if (this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// 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. | ||
|
||
using Microsoft.CodeAnalysis.CSharp.Test.Utilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UnitTests; | ||
|
||
public sealed class PartialEventsAndConstructorsTests : CSharpTestBase | ||
{ | ||
[Fact] | ||
public void ReturningPartialType_LocalFunction_InMethod() | ||
{ | ||
var source = """ | ||
class @partial | ||
{ | ||
static void Main() | ||
{ | ||
System.Console.Write(F().GetType().Name); | ||
partial F() => new(); | ||
} | ||
} | ||
"""; | ||
CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: "partial").VerifyDiagnostics(); | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var expectedDiagnostics = new[] | ||
{ | ||
// (5,30): error CS0103: The name 'F' does not exist in the current context | ||
// System.Console.Write(F().GetType().Name); | ||
Diagnostic(ErrorCode.ERR_NameNotInContext, "F").WithArguments("F").WithLocation(5, 30), | ||
// (5,50): error CS1513: } expected | ||
// System.Console.Write(F().GetType().Name); | ||
Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(5, 50), | ||
// (6,9): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it will be in the next PR (I already have that locally so I would skip adding a prototype comment for that if that's okay) |
||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(6, 9), | ||
// (6,17): error CS1520: Method must have a return type | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_MemberNeedsType, "F").WithLocation(6, 17), | ||
// (6,24): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_IllegalStatement, "new()").WithLocation(6, 24), | ||
// (8,1): error CS1022: Type or namespace definition, or end-of-file expected | ||
// } | ||
Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(8, 1) | ||
}; | ||
|
||
CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); | ||
CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); | ||
} | ||
|
||
[Fact] | ||
public void ReturningPartialType_LocalFunction_TopLevel() | ||
{ | ||
var source = """ | ||
System.Console.Write(F().GetType().Name); | ||
partial F() => new(); | ||
class @partial; | ||
"""; | ||
CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: "partial").VerifyDiagnostics(); | ||
|
||
var expectedDiagnostics = new[] | ||
{ | ||
// (1,22): error CS0103: The name 'F' does not exist in the current context | ||
// System.Console.Write(F().GetType().Name); | ||
Diagnostic(ErrorCode.ERR_NameNotInContext, "F").WithArguments("F").WithLocation(1, 22), | ||
// (2,9): error CS0116: A namespace cannot directly contain members such as fields, methods or statements | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "F").WithLocation(2, 9), | ||
// (2,10): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_IllegalStatement, "() => new()").WithLocation(2, 10) | ||
}; | ||
|
||
CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); | ||
CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); | ||
} | ||
|
||
[Fact] | ||
public void ReturningPartialType_Method() | ||
{ | ||
var source = """ | ||
class C | ||
{ | ||
partial F() => new(); | ||
static void Main() | ||
{ | ||
System.Console.Write(new C().F().GetType().Name); | ||
} | ||
} | ||
class @partial; | ||
"""; | ||
CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: "partial").VerifyDiagnostics(); | ||
|
||
var expectedDiagnostics = new[] | ||
{ | ||
// (3,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(3, 5), | ||
// (3,13): error CS1520: Method must have a return type | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_MemberNeedsType, "F").WithLocation(3, 13), | ||
// (3,20): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement | ||
// partial F() => new(); | ||
Diagnostic(ErrorCode.ERR_IllegalStatement, "new()").WithLocation(3, 20), | ||
// (6,38): error CS1061: 'C' does not contain a definition for 'F' and no accessible extension method 'F' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) | ||
// System.Console.Write(new C().F().GetType().Name); | ||
Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "F").WithArguments("C", "F").WithLocation(6, 38) | ||
}; | ||
|
||
CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); | ||
CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.