Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,20 @@ CSharpCompilation.Create("test",

Similarly this can be observed when using the command-line argument `/refonly`
or the `ProduceOnlyReferenceAssembly` MSBuild property.

## `partial` cannot be a return type of methods

***Introduced in Visual Studio 2022 version 17.14***

The [partial events and constructors](https://github.com/dotnet/csharplang/issues/9058) language feature
allows the `partial` modifier in more places and so it cannot be a return type unless escaped:

```cs
class C
{
partial F() => new partial(); // previously worked
@partial F() => new partial(); // workaround
}

class partial { }
```
Original file line number Diff line number Diff line change
Expand Up @@ -598,25 +598,16 @@ bool checkSymbol(Symbol sym, TextSpan memberSpan, SymbolKind kind, out Symbol re
return false;
}

if (kind is SymbolKind.Method or SymbolKind.Property)
if (kind is SymbolKind.Method or SymbolKind.Property or SymbolKind.Event)
{
if (InSpan(sym.GetFirstLocation(), this.syntaxTree, memberSpan))
{
return true;
}

// If this is a partial member, the member represents the defining part,
// not the implementation (member.Locations includes both parts). If the
// span is in fact in the implementation, return that member instead.
if (sym switch
#pragma warning disable format
{
MethodSymbol method => (Symbol)method.PartialImplementationPart,
SourcePropertySymbol property => property.PartialImplementationPart,
_ => throw ExceptionUtilities.UnexpectedValue(sym)
}
#pragma warning restore format
is { } implementation)
// If this is a partial member, the member represents the defining part, not the implementation.
// If the span is in fact in the implementation, return that member instead.
if (sym.GetPartialImplementationPart() is { } implementation)
{
if (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan))
{
Expand Down
35 changes: 28 additions & 7 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@
<value>Cannot implicitly convert type '{0}' to '{1}'. An explicit conversion exists (are you missing a cast?)</value>
</data>
<data name="ERR_PartialMisplaced" xml:space="preserve">
<value>The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type.</value>
<value>The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', 'event', an instance constructor name, or a method or property return type.</value>
</data>
<data name="ERR_ImportedCircularBase" xml:space="preserve">
<value>Imported type '{0}' is invalid. It contains a circular base type dependency.</value>
Expand Down Expand Up @@ -7983,14 +7983,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_PartialPropertyInitMismatch" xml:space="preserve">
<value>Property accessor '{0}' must be '{1}' to match the definition part</value>
</data>
<data name="ERR_PartialPropertyTypeDifference" xml:space="preserve">
<value>Both partial property declarations must have the same type.</value>
<data name="ERR_PartialMemberTypeDifference" xml:space="preserve">
<value>Both partial member declarations must have the same type.</value>
</data>
<data name="WRN_PartialPropertySignatureDifference" xml:space="preserve">
<value>Partial property declarations '{0}' and '{1}' have signature differences.</value>
<data name="WRN_PartialMemberSignatureDifference" xml:space="preserve">
<value>Partial member declarations '{0}' and '{1}' have signature differences.</value>
</data>
<data name="WRN_PartialPropertySignatureDifference_Title" xml:space="preserve">
<value>Partial property declarations have signature differences.</value>
<data name="WRN_PartialMemberSignatureDifference_Title" xml:space="preserve">
<value>Partial member declarations have signature differences.</value>
</data>
<data name="ERR_PartialPropertyRequiredDifference" xml:space="preserve">
<value>Both partial property declarations must be required or neither may be required</value>
Expand Down Expand Up @@ -8050,4 +8050,25 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_DataSectionStringLiteralHashCollision" xml:space="preserve">
<value>Cannot emit this string literal into the data section because it has XXHash128 collision with another string literal: {0}</value>
</data>
<data name="IDS_FeaturePartialEventsAndConstructors" xml:space="preserve">
<value>partial events and constructors</value>
</data>
<data name="ERR_PartialMemberMissingImplementation" xml:space="preserve">
<value>Partial member '{0}' must have an implementation part.</value>
</data>
<data name="ERR_PartialMemberMissingDefinition" xml:space="preserve">
<value>Partial member '{0}' must have a definition part.</value>
</data>
<data name="ERR_PartialMemberDuplicateDefinition" xml:space="preserve">
<value>Partial member '{0}' may not have multiple defining declarations.</value>
</data>
<data name="ERR_PartialMemberDuplicateImplementation" xml:space="preserve">
<value>Partial member '{0}' may not have multiple implementing declarations.</value>
</data>
<data name="ERR_PartialEventInitializer" xml:space="preserve">
<value>'{0}': partial event cannot have initializer</value>
</data>
<data name="ERR_PartialConstructorInitializer" xml:space="preserve">
<value>'{0}': only the implementing declaration of a partial constructor can have an initializer</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -1800,13 +1800,7 @@ private Symbol GetDeclaredMember(NamespaceOrTypeSymbol container, TextSpan decla
}

// Handle the case of the implementation of a partial member.
Symbol partial = symbol switch
{
MethodSymbol method => method.PartialImplementationPart,
SourcePropertySymbol property => property.PartialImplementationPart,
_ => null
};

Symbol partial = symbol.GetPartialImplementationPart();
if ((object)partial != null)
{
var loc = partial.GetFirstLocation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,7 @@ public override void DefaultVisit(Symbol symbol)
bool shouldSkipPartialDefinitionComments = false;
if (symbol.IsPartialDefinition())
{
Symbol? implementationPart = symbol switch
{
MethodSymbol method => method.PartialImplementationPart,
SourcePropertySymbol property => property.PartialImplementationPart,
_ => null
};

Symbol? implementationPart = symbol.GetPartialImplementationPart();
if (implementationPart is not null)
{
Visit(implementationPart);
Expand Down
11 changes: 9 additions & 2 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2332,8 +2332,8 @@ internal enum ErrorCode
ERR_PartialPropertyMissingAccessor = 9252,
ERR_PartialPropertyUnexpectedAccessor = 9253,
ERR_PartialPropertyInitMismatch = 9254,
ERR_PartialPropertyTypeDifference = 9255,
WRN_PartialPropertySignatureDifference = 9256,
ERR_PartialMemberTypeDifference = 9255,
WRN_PartialMemberSignatureDifference = 9256,
ERR_PartialPropertyRequiredDifference = 9257,

WRN_FieldIsAmbiguous = 9258,
Expand Down Expand Up @@ -2362,6 +2362,13 @@ internal enum ErrorCode

ERR_DataSectionStringLiteralHashCollision = 9274,

ERR_PartialMemberMissingImplementation = 9275,
ERR_PartialMemberMissingDefinition = 9276,
ERR_PartialMemberDuplicateDefinition = 9277,
ERR_PartialMemberDuplicateImplementation = 9278,
ERR_PartialEventInitializer = 9279,
ERR_PartialConstructorInitializer = 9280,

// Note: you will need to do the following after adding errors:
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)

Expand Down
12 changes: 9 additions & 3 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_CollectionExpressionRefStructMayAllocate:
case ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate:
case ErrorCode.WRN_ConvertingLock:
case ErrorCode.WRN_PartialPropertySignatureDifference:
case ErrorCode.WRN_PartialMemberSignatureDifference:
case ErrorCode.WRN_FieldIsAmbiguous:
case ErrorCode.WRN_UninitializedNonNullableBackingField:
case ErrorCode.WRN_AccessorDoesNotUseBackingField:
Expand Down Expand Up @@ -2464,8 +2464,8 @@ or ErrorCode.ERR_PartialPropertyDuplicateImplementation
or ErrorCode.ERR_PartialPropertyMissingAccessor
or ErrorCode.ERR_PartialPropertyUnexpectedAccessor
or ErrorCode.ERR_PartialPropertyInitMismatch
or ErrorCode.ERR_PartialPropertyTypeDifference
or ErrorCode.WRN_PartialPropertySignatureDifference
or ErrorCode.ERR_PartialMemberTypeDifference
or ErrorCode.WRN_PartialMemberSignatureDifference
or ErrorCode.ERR_PartialPropertyRequiredDifference
or ErrorCode.WRN_FieldIsAmbiguous
or ErrorCode.ERR_InlineArrayAttributeOnRecord
Expand All @@ -2481,6 +2481,12 @@ or ErrorCode.WRN_UnscopedRefAttributeOldRules
or ErrorCode.WRN_InterceptsLocationAttributeUnsupportedSignature
or ErrorCode.ERR_ImplicitlyTypedParamsParameter
or ErrorCode.ERR_VariableDeclarationNamedField
or ErrorCode.ERR_PartialMemberMissingImplementation
or ErrorCode.ERR_PartialMemberMissingDefinition
or ErrorCode.ERR_PartialMemberDuplicateDefinition
or ErrorCode.ERR_PartialMemberDuplicateImplementation
or ErrorCode.ERR_PartialEventInitializer
or ErrorCode.ERR_PartialConstructorInitializer
=> false,
};
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ internal enum MessageID
IDS_FeatureFirstClassSpan = MessageBase + 12849,
IDS_FeatureUnboundGenericTypesInNameof = MessageBase + 12850,
IDS_FeatureSimpleLambdaParameterModifiers = MessageBase + 12851,

IDS_FeaturePartialEventsAndConstructors = MessageBase + 12852,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -480,6 +482,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureFirstClassSpan:
case MessageID.IDS_FeatureUnboundGenericTypesInNameof:
case MessageID.IDS_FeatureSimpleLambdaParameterModifiers:
case MessageID.IDS_FeaturePartialEventsAndConstructors:
return LanguageVersion.Preview;

// C# 13.0 features.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 20 additions & 19 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

// Check for:
// partial event
if (this.PeekToken(1).Kind == SyntaxKind.EventKeyword)
{
return true;
}

// Check for constructor:
// partial Identifier(
if (this.PeekToken(1).Kind == SyntaxKind.IdentifierToken &&
this.PeekToken(2).Kind == SyntaxKind.OpenParenToken)
{
return IsFeatureEnabled(MessageID.IDS_FeaturePartialEventsAndConstructors);
}

// Here we check for:
// Check for method/property:
// partial ReturnType MemberName
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword);
using var _ = this.GetDisposableResetPoint(resetOnDispose: true);

this.EatToken(); // partial
Expand Down Expand Up @@ -5681,7 +5682,7 @@ private bool IsTrueIdentifier()
{
if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken)
{
if (!IsCurrentTokenPartialKeywordOfPartialMethodOrType() &&
if (!IsCurrentTokenPartialKeywordOfPartialMemberOrType() &&
!IsCurrentTokenQueryKeywordInQuery() &&
!IsCurrentTokenWhereOfConstraintClause())
{
Expand Down Expand Up @@ -5728,7 +5729,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);
Expand All @@ -5755,7 +5756,7 @@ private bool IsCurrentTokenQueryKeywordInQuery()
return this.IsInQuery && this.IsCurrentTokenQueryContextualKeyword;
}

private bool IsCurrentTokenPartialKeywordOfPartialMethodOrType()
private bool IsCurrentTokenPartialKeywordOfPartialMemberOrType()
{
if (this.CurrentToken.ContextualKind == SyntaxKind.PartialKeyword)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/EventSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ internal virtual bool IsExplicitInterfaceImplementation
/// </remarks>
public abstract ImmutableArray<EventSymbol> ExplicitInterfaceImplementations { get; }

internal virtual EventSymbol? PartialImplementationPart => null;
internal virtual EventSymbol? PartialDefinitionPart => null;
internal virtual bool IsPartialDefinition => false;

/// <summary>
/// Gets the kind of this symbol.
/// </summary>
Expand Down
41 changes: 38 additions & 3 deletions src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,10 @@ internal static bool IsPartialMember(this Symbol member)
return member
is SourceOrdinaryMethodSymbol { IsPartial: true }
or SourcePropertySymbol { IsPartial: true }
or SourcePropertyAccessorSymbol { IsPartial: true };
or SourcePropertyAccessorSymbol { IsPartial: true }
or SourceConstructorSymbol { IsPartial: true }
or SourceEventSymbol { IsPartial: true }
or SourceEventAccessorSymbol { IsPartial: true };
}

internal static bool IsPartialImplementation(this Symbol member)
Expand All @@ -560,7 +563,10 @@ internal static bool IsPartialImplementation(this Symbol member)
return member
is SourceOrdinaryMethodSymbol { IsPartialImplementation: true }
or SourcePropertySymbol { IsPartialImplementation: true }
or SourcePropertyAccessorSymbol { IsPartialImplementation: true };
or SourcePropertyAccessorSymbol { IsPartialImplementation: true }
or SourceConstructorSymbol { IsPartialImplementation: true }
or SourceEventSymbol { IsPartialImplementation: true }
or SourceEventAccessorSymbol { IsPartialImplementation: true };
}

internal static bool IsPartialDefinition(this Symbol member)
Expand All @@ -569,9 +575,38 @@ internal static bool IsPartialDefinition(this Symbol member)
return member
is SourceOrdinaryMethodSymbol { IsPartialDefinition: true }
or SourcePropertySymbol { IsPartialDefinition: true }
or SourcePropertyAccessorSymbol { IsPartialDefinition: true };
or SourcePropertyAccessorSymbol { IsPartialDefinition: true }
or SourceConstructorSymbol { IsPartialDefinition: true }
or SourceEventSymbol { IsPartialDefinition: true }
or SourceEventAccessorSymbol { IsPartialDefinition: true };
}

#nullable enable
internal static Symbol? GetPartialImplementationPart(this Symbol member)
{
Debug.Assert(member.IsDefinition);
return member switch
{
MethodSymbol method => method.PartialImplementationPart,
SourcePropertySymbol property => property.PartialImplementationPart,
SourceEventSymbol ev => ev.PartialImplementationPart,
_ => null,
};
}

internal static Symbol? GetPartialDefinitionPart(this Symbol member)
{
Debug.Assert(member.IsDefinition);
return member switch
{
MethodSymbol method => method.PartialDefinitionPart,
SourcePropertySymbol property => property.PartialDefinitionPart,
SourceEventSymbol ev => ev.PartialDefinitionPart,
_ => null,
};
}
#nullable disable

internal static bool ContainsTupleNames(this Symbol member)
{
switch (member.Kind)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ ImmutableArray<IEventSymbol> IEventSymbol.ExplicitInterfaceImplementations

bool IEventSymbol.IsWindowsRuntimeEvent => _underlying.IsWindowsRuntimeEvent;

IEventSymbol? IEventSymbol.PartialDefinitionPart => _underlying.PartialDefinitionPart.GetPublicSymbol();

IEventSymbol? IEventSymbol.PartialImplementationPart => _underlying.PartialImplementationPart.GetPublicSymbol();

bool IEventSymbol.IsPartialDefinition => _underlying.IsPartialDefinition;

#region ISymbol Members

protected override void Accept(SymbolVisitor visitor)
Expand Down
Loading
Loading