From f23ba9a4fc22279d6c49a0165ce4564890aa1855 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 18 Apr 2024 15:49:10 -0700 Subject: [PATCH 1/6] Partial properties: merging declarations and emit of simple cases --- .../BinderFactory.BinderFactoryVisitor.cs | 22 + .../CSharp/Portable/CSharpResources.resx | 20 +- .../CSharp/Portable/Errors/ErrorCode.cs | 8 + .../CSharp/Portable/Errors/ErrorFacts.cs | 7 + .../Symbols/MemberSymbolExtensions.cs | 22 +- .../Source/SourceMemberContainerSymbol.cs | 196 +++++++-- .../SourceMethodSymbolWithAttributes.cs | 2 +- .../Symbols/Source/SourceParameterSymbol.cs | 2 +- .../Source/SourcePropertyAccessorSymbol.cs | 14 +- .../Symbols/Source/SourcePropertySymbol.cs | 54 ++- .../Source/SourcePropertySymbolBase.cs | 16 +- ...nthesizedRecordEqualityContractProperty.cs | 1 + .../SynthesizedRecordPropertySymbol.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 34 +- .../Portable/xlf/CSharpResources.de.xlf | 34 +- .../Portable/xlf/CSharpResources.es.xlf | 34 +- .../Portable/xlf/CSharpResources.fr.xlf | 34 +- .../Portable/xlf/CSharpResources.it.xlf | 34 +- .../Portable/xlf/CSharpResources.ja.xlf | 34 +- .../Portable/xlf/CSharpResources.ko.xlf | 34 +- .../Portable/xlf/CSharpResources.pl.xlf | 34 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 34 +- .../Portable/xlf/CSharpResources.ru.xlf | 34 +- .../Portable/xlf/CSharpResources.tr.xlf | 34 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 34 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 34 +- .../Emit2/Diagnostics/GetDiagnosticsTests.cs | 2 + .../DefaultInterfaceImplementationTests.cs | 12 +- .../Symbol/Symbols/PartialPropertiesTests.cs | 409 ++++++++++++++++++ .../Test/Symbol/Symbols/SymbolErrorTests.cs | 10 +- 30 files changed, 1144 insertions(+), 96 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 39e48112f4f15..770f45d21f017 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -288,6 +288,7 @@ public override Binder VisitAccessorDeclaration(AccessorDeclarationSyntax parent if ((object)propertySymbol != null) { accessor = (parent.Kind() == SyntaxKind.GetAccessorDeclaration) ? propertySymbol.GetMethod : propertySymbol.SetMethod; + Debug.Assert(accessor is not null || parent.HasErrors); } break; } @@ -605,6 +606,27 @@ bool checkSymbol(Symbol sym, TextSpan memberSpan, SymbolKind kind, out Symbol re } } } + else if (sym.Kind == SymbolKind.Property) + { + if (InSpan(sym.GetFirstLocation(), this.syntaxTree, memberSpan)) + { + return true; + } + + // If this is a partial property, the property represents the defining part, + // not the implementation (property.Locations includes both parts). If the + // span is in fact in the implementation, return that property instead. + var property = (SourcePropertySymbol)sym; + var implementation = property.IsPartialDefinition ? property.OtherPartOfPartial : property; + if ((object)implementation != null) + { + if (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan)) + { + result = implementation; + return true; + } + } + } else if (InSpan(sym.Locations, this.syntaxTree, memberSpan)) { return true; diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 5d47bd91d0474..dd9894980cfb4 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -1171,7 +1171,7 @@ Cannot implicitly convert type '{0}' to '{1}'. An explicit conversion exists (are you missing a cast?) - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. Imported type '{0}' is invalid. It contains a circular base type dependency. @@ -7929,4 +7929,22 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'. + + Partial property '{0}' must have an implementation part. + + + Partial property '{0}' must have an definition part. + + + A partial property may not have multiple defining declarations + + + A partial property may not have multiple implementing declarations + + + Property accessor '{0}' must be implemented because it is declared on the definition part + + + Property accessor '{0}' does not implement any accessor declared on the definition part + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index d04040b6dff27..8a92d083d5d79 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2310,6 +2310,14 @@ internal enum ErrorCode ERR_InterceptsLocationDataInvalidPosition = 9235, INF_TooManyBoundLambdas = 9236, + // PROTOTYPE(partial-properties): pack + ERR_PartialPropertyMissingImplementation = 9300, + ERR_PartialPropertyMissingDefinition = 9301, + ERR_PartialPropertyDuplicateDefinition = 9302, + ERR_PartialPropertyDuplicateImplementation = 9303, + ERR_PartialPropertyMissingAccessor = 9304, + ERR_PartialPropertyUnexpectedAccessor = 9305, + #endregion // Note: you will need to do the following after adding errors: diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 6ca5b85258d76..ab3f3d79a5da8 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2438,6 +2438,13 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_InterceptsLocationFileNotFound: case ErrorCode.ERR_InterceptsLocationDataInvalidPosition: case ErrorCode.INF_TooManyBoundLambdas: + + case ErrorCode.ERR_PartialPropertyMissingImplementation: + case ErrorCode.ERR_PartialPropertyMissingDefinition: + case ErrorCode.ERR_PartialPropertyDuplicateDefinition: + case ErrorCode.ERR_PartialPropertyDuplicateImplementation: + case ErrorCode.ERR_PartialPropertyMissingAccessor: + case ErrorCode.ERR_PartialPropertyUnexpectedAccessor: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 3855f36d82f99..3c26b0badd607 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -474,7 +474,7 @@ internal static bool ShouldEmit(this MethodSymbol method) } // Don't emit partial methods without an implementation part. - if (method.IsPartialMethod() && method.PartialImplementationPart is null) + if (method.IsPartialMember() && method.PartialImplementationPart is null) { return false; } @@ -545,22 +545,28 @@ internal static bool IsExplicitInterfaceImplementation(this Symbol member) } } - internal static bool IsPartialMethod(this Symbol member) + internal static bool IsPartialMember(this Symbol member) { - var sms = member as SourceMemberMethodSymbol; - return sms?.IsPartial == true; + return member + is SourceOrdinaryMethodSymbol { IsPartial: true } + or SourcePropertySymbol { IsPartial: true } + or SourcePropertyAccessorSymbol { IsPartial: true }; } internal static bool IsPartialImplementation(this Symbol member) { - var sms = member as SourceOrdinaryMethodSymbol; - return sms?.IsPartialImplementation == true; + return member + is SourceOrdinaryMethodSymbol { IsPartialImplementation: true } + or SourcePropertySymbol { IsPartialImplementation: true } + or SourcePropertyAccessorSymbol { IsPartialImplementation: true }; } internal static bool IsPartialDefinition(this Symbol member) { - var sms = member as SourceOrdinaryMethodSymbol; - return sms?.IsPartialDefinition == true; + return member + is SourceOrdinaryMethodSymbol { IsPartialDefinition: true } + or SourcePropertySymbol { IsPartialDefinition: true } + or SourcePropertyAccessorSymbol { IsPartialDefinition: true }; } internal static bool ContainsTupleNames(this Symbol member) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 97628686b76fa..b31d9b5ec2640 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3574,68 +3574,168 @@ private void MergePartialMembers( memberNames.AddRange(membersByName.Keys); //key and value will be the same object - var methodsBySignature = new Dictionary(MemberSignatureComparer.PartialMethodsComparer); + var membersBySignature = new Dictionary(MemberSignatureComparer.PartialMethodsComparer); foreach (var name in memberNames) { - methodsBySignature.Clear(); + membersBySignature.Clear(); foreach (var symbol in membersByName[name]) { - var method = symbol as SourceMemberMethodSymbol; - if (method is null || !method.IsPartial) + if (!symbol.IsPartialMember()) { - continue; // only partial methods need to be merged + continue; } - if (methodsBySignature.TryGetValue(method, out var prev)) + if (!membersBySignature.TryGetValue(symbol, out var prev)) { - var prevPart = (SourceOrdinaryMethodSymbol)prev; - var methodPart = (SourceOrdinaryMethodSymbol)method; + membersBySignature.Add(symbol, symbol); + continue; + } - if (methodPart.IsPartialImplementation && - (prevPart.IsPartialImplementation || (prevPart.OtherPartOfPartial is MethodSymbol otherImplementation && (object)otherImplementation != methodPart))) - { - // A partial method may not have multiple implementing declarations - diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneActual, methodPart.GetFirstLocation()); - } - else if (methodPart.IsPartialDefinition && - (prevPart.IsPartialDefinition || (prevPart.OtherPartOfPartial is MethodSymbol otherDefinition && (object)otherDefinition != methodPart))) - { - // A partial method may not have multiple defining declarations - diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneLatent, methodPart.GetFirstLocation()); - } - else - { - if ((object)membersByName == _lazyEarlyAttributeDecodingMembersDictionary) + Debug.Assert(symbol.GetType() == prev.GetType()); + switch (symbol, prev) + { + case (SourceOrdinaryMethodSymbol currentMethod, SourceOrdinaryMethodSymbol prevMethod): + mergePartialMethods(ref membersByName, name, currentMethod, prevMethod); + break; + + case (SourcePropertySymbol currentProperty, SourcePropertySymbol prevProperty): + mergePartialProperties(ref membersByName, name, currentProperty, prevProperty); + break; + + case (SourcePropertyAccessorSymbol, SourcePropertyAccessorSymbol): + break; // accessor symbols and their diagnostics are handled by processing the associated property + + default: + throw ExceptionUtilities.UnexpectedValue(prev); + } + } + + foreach (var symbol in membersBySignature.Values) + { + switch (symbol) + { + case SourceOrdinaryMethodSymbol method: + // partial implementations not paired with a definition + if (method.IsPartialImplementation && method.OtherPartOfPartial is null) { - // Avoid mutating the cached dictionary and especially avoid doing this possibly on multiple threads in parallel. - membersByName = new Dictionary, ImmutableArray>(membersByName, ReadOnlyMemoryOfCharComparer.Instance); + diagnostics.Add(ErrorCode.ERR_PartialMethodMustHaveLatent, method.GetFirstLocation(), method); } + else if (method is { IsPartialDefinition: true, OtherPartOfPartial: null, HasExplicitAccessModifier: true }) + { + diagnostics.Add(ErrorCode.ERR_PartialMethodWithAccessibilityModsMustHaveImplementation, method.GetFirstLocation(), method); + } + break; - membersByName[name] = FixPartialMember(membersByName[name], prevPart, methodPart); - } + case SourcePropertySymbol property: + if (property.OtherPartOfPartial is null) + { + diagnostics.Add( + property.IsPartialDefinition ? ErrorCode.ERR_PartialPropertyMissingImplementation : ErrorCode.ERR_PartialPropertyMissingDefinition, + property.GetFirstLocation(), + property); + } + break; + + case SourcePropertyAccessorSymbol: + break; // handled by SourcePropertySymbol case + + default: + throw ExceptionUtilities.UnexpectedValue(symbol); } - else + } + } + + memberNames.Free(); + + void mergePartialMethods(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourceOrdinaryMethodSymbol currentMethod, SourceOrdinaryMethodSymbol prevMethod) + { + if (currentMethod.IsPartialImplementation && + (prevMethod.IsPartialImplementation || (prevMethod.OtherPartOfPartial is MethodSymbol otherImplementation && (object)otherImplementation != currentMethod))) + { + // A partial method may not have multiple implementing declarations + diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneActual, currentMethod.GetFirstLocation()); + } + else if (currentMethod.IsPartialDefinition && + (prevMethod.IsPartialDefinition || (prevMethod.OtherPartOfPartial is MethodSymbol otherDefinition && (object)otherDefinition != currentMethod))) + { + // A partial method may not have multiple defining declarations + diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneLatent, currentMethod.GetFirstLocation()); + } + else + { + if ((object)membersByName == _lazyEarlyAttributeDecodingMembersDictionary) { - methodsBySignature.Add(method, method); + // Avoid mutating the cached dictionary and especially avoid doing this possibly on multiple threads in parallel. + membersByName = new Dictionary, ImmutableArray>(membersByName, ReadOnlyMemoryOfCharComparer.Instance); } + + membersByName[name] = FixPartialMember(membersByName[name], prevMethod, currentMethod); } + } - foreach (SourceOrdinaryMethodSymbol method in methodsBySignature.Values) + void mergePartialProperties(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertySymbol currentProperty, SourcePropertySymbol prevProperty) + { + if (currentProperty.IsPartialImplementation && + (prevProperty.IsPartialImplementation || (prevProperty.OtherPartOfPartial is SourcePropertySymbol otherImplementation && (object)otherImplementation != currentProperty))) + { + diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateImplementation, currentProperty.GetFirstLocation()); + } + else if (currentProperty.IsPartialDefinition && + (prevProperty.IsPartialDefinition || (prevProperty.OtherPartOfPartial is SourcePropertySymbol otherDefinition && (object)otherDefinition != currentProperty))) + { + diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateDefinition, currentProperty.GetFirstLocation()); + } + else { - // partial implementations not paired with a definition - if (method.IsPartialImplementation && method.OtherPartOfPartial is null) + var (currentGet, prevGet) = ((SourcePropertyAccessorSymbol?)currentProperty.GetMethod, (SourcePropertyAccessorSymbol?)prevProperty.GetMethod); + if (currentGet != null || prevGet != null) { - diagnostics.Add(ErrorCode.ERR_PartialMethodMustHaveLatent, method.GetFirstLocation(), method); + var accessorName = (currentGet ?? prevGet)!.Name.AsMemory(); + mergeAccessors(ref membersByName, accessorName, currentGet, prevGet); } - else if (method is { IsPartialDefinition: true, OtherPartOfPartial: null, HasExplicitAccessModifier: true }) + + var (currentSet, prevSet) = ((SourcePropertyAccessorSymbol?)currentProperty.SetMethod, (SourcePropertyAccessorSymbol?)prevProperty.SetMethod); + if (currentSet != null || prevSet != null) { - diagnostics.Add(ErrorCode.ERR_PartialMethodWithAccessibilityModsMustHaveImplementation, method.GetFirstLocation(), method); + var accessorName = (currentSet ?? prevSet)!.Name.AsMemory(); + mergeAccessors(ref membersByName, accessorName, currentSet, prevSet); } + + if ((object)membersByName == _lazyEarlyAttributeDecodingMembersDictionary) + { + // Avoid mutating the cached dictionary and especially avoid doing this possibly on multiple threads in parallel. + membersByName = new Dictionary, ImmutableArray>(membersByName, ReadOnlyMemoryOfCharComparer.Instance); + } + + membersByName[name] = FixPartialMember(membersByName[name], prevProperty, currentProperty); } - } - memberNames.Free(); + void mergeAccessors(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertyAccessorSymbol? currentAccessor, SourcePropertyAccessorSymbol? prevAccessor) + { + Debug.Assert(currentAccessor != null || prevAccessor != null); + + // When an accessor is present on definition but not on implementation, the accessor is said to be missing on the implementation. + // When an accessor is present on implementation but not on definition, the accessor is said to be unexpected on the implementation. + if (currentAccessor == null) + { + // Partial definition is the source of truth for which accessors should be present. + var (errorCode, propertyToBlame) = prevProperty.IsPartialDefinition ? (ErrorCode.ERR_PartialPropertyMissingAccessor, currentProperty) : (ErrorCode.ERR_PartialPropertyUnexpectedAccessor, prevProperty); + diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), prevAccessor!); + } + else if (prevAccessor == null) + { + // Partial definition is the source of truth for which accessors should be present. + var (errorCode, propertyToBlame) = currentProperty.IsPartialDefinition ? (ErrorCode.ERR_PartialPropertyMissingAccessor, prevProperty) : (ErrorCode.ERR_PartialPropertyUnexpectedAccessor, currentProperty); + diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), currentAccessor!); + } + else + { + var (definitionAccessor, implementationAccessor) = currentProperty.IsPartialDefinition ? (currentAccessor, prevAccessor) : (prevAccessor, currentAccessor); + membersByName[name] = Remove(membersByName[name], implementationAccessor); + } + } + } } /// @@ -3667,6 +3767,28 @@ private static ImmutableArray FixPartialMember(ImmutableArray sy return Remove(symbols, implementation); } + // PROTOTYPE(partial-properties): is there some abstraction that would make this nice? + private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourcePropertySymbol part1, SourcePropertySymbol part2) + { + SourcePropertySymbol definition; + SourcePropertySymbol implementation; + if (part1.IsPartialDefinition) + { + definition = part1; + implementation = part2; + } + else + { + definition = part2; + implementation = part1; + } + + SourcePropertySymbol.InitializePartialPropertyParts(definition, implementation); + + // a partial method is represented in the member list by its definition part: + return Remove(symbols, implementation); + } + private static ImmutableArray Remove(ImmutableArray symbols, Symbol symbol) { var builder = ArrayBuilder.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 70bbd0a42458f..650b9b0889d13 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -1452,7 +1452,7 @@ internal override void PostDecodeWellKnownAttributes(ImmutableArray _property is SourcePropertySymbol { IsPartialDefinition: true, OtherPartOfPartial: { } other } + ? (MethodKind == MethodKind.PropertyGet ? other.GetMethod : other.SetMethod) + : null; + + public sealed override MethodSymbol? PartialDefinitionPart => _property is SourcePropertySymbol { IsPartialImplementation: true, OtherPartOfPartial: { } other } + ? (MethodKind == MethodKind.PropertyGet ? other.GetMethod : other.SetMethod) + : null; + + internal bool IsPartialDefinition => _property is SourcePropertySymbol { IsPartialDefinition: true }; + internal bool IsPartialImplementation => _property is SourcePropertySymbol { IsPartialImplementation: true }; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index af93d12c934ff..08356a3534ad6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -13,6 +13,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SourcePropertySymbol : SourcePropertySymbolBase { + private SourcePropertySymbol? _otherPartOfPartial; + internal static SourcePropertySymbol Create(SourceMemberContainerTypeSymbol containingType, Binder bodyBinder, PropertyDeclarationSyntax syntax, BindingDiagnosticBag diagnostics) { var nameToken = syntax.Identifier; @@ -37,7 +39,6 @@ private static SourcePropertySymbol Create( GetAccessorDeclarations( syntax, diagnostics, - out bool isAutoProperty, out bool hasAccessorList, out bool accessorsHaveImplementation, out bool isInitOnly, @@ -57,6 +58,8 @@ private static SourcePropertySymbol Create( diagnostics, out _); + // PROTOTYPE(partial-properties): it's unclear if the subtle difference between former 'isAutoProperty' and '!accessorsHaveImplementation' matters + bool isAutoProperty = (modifiers & DeclarationModifiers.Partial) == 0 && !accessorsHaveImplementation; bool isExpressionBodied = !hasAccessorList && GetArrowExpression(syntax) != null; binder = binder.WithUnsafeRegionIfNecessary(modifiersTokenList); @@ -76,6 +79,7 @@ private static SourcePropertySymbol Create( isAutoProperty: isAutoProperty, isExpressionBodied: isExpressionBodied, isInitOnly: isInitOnly, + accessorsHaveImplementation: accessorsHaveImplementation, memberName, location, diagnostics); @@ -93,6 +97,7 @@ private SourcePropertySymbol( bool isAutoProperty, bool isExpressionBodied, bool isInitOnly, + bool accessorsHaveImplementation, string memberName, Location location, BindingDiagnosticBag diagnostics) @@ -109,6 +114,7 @@ private SourcePropertySymbol( isAutoProperty: isAutoProperty, isExpressionBodied: isExpressionBodied, isInitOnly: isInitOnly, + accessorsHaveImplementation: accessorsHaveImplementation, syntax.Type.SkipScoped(out _).GetRefKindInLocalOrReturn(diagnostics), memberName, syntax.AttributeLists, @@ -163,7 +169,6 @@ public override SyntaxList AttributeDeclarationSyntaxList private static void GetAccessorDeclarations( CSharpSyntaxNode syntaxNode, BindingDiagnosticBag diagnostics, - out bool isAutoProperty, out bool hasAccessorList, out bool accessorsHaveImplementation, out bool isInitOnly, @@ -171,7 +176,6 @@ private static void GetAccessorDeclarations( out CSharpSyntaxNode? setSyntax) { var syntax = (BasePropertyDeclarationSyntax)syntaxNode; - isAutoProperty = true; hasAccessorList = syntax.AccessorList != null; getSyntax = null; setSyntax = null; @@ -223,14 +227,12 @@ private static void GetAccessorDeclarations( if (accessor.Body != null || accessor.ExpressionBody != null) { - isAutoProperty = false; accessorsHaveImplementation = true; } } } else { - isAutoProperty = false; accessorsHaveImplementation = GetArrowExpression(syntax) is object; } } @@ -284,6 +286,7 @@ private static DeclarationModifiers MakeModifiers( if (!isExplicitInterfaceImplementation) { allowedModifiers |= DeclarationModifiers.New | + DeclarationModifiers.Partial | DeclarationModifiers.Sealed | DeclarationModifiers.Abstract | DeclarationModifiers.Virtual | @@ -552,9 +555,50 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } diagnostics.Add(Location, useSiteInfo); + + if (IsPartialDefinition && OtherPartOfPartial is { } implementation) + { + PartialPropertyChecks(implementation, diagnostics); + } + } + + private void PartialPropertyChecks(SourcePropertySymbol implementation, BindingDiagnosticBag diagnostics) + { + Debug.Assert(this.IsPartialDefinition); + Debug.Assert((object)this != implementation); + Debug.Assert((object?)this.OtherPartOfPartial == implementation); + + // PROTOTYPE(partial-properties): check and diagnose all disallowed differences between parts. + // PROTOTYPE(partial-properties): we should likely report a warning when only one of the parts has an explicit access modifier for consistency with methods. + if (DeclaredAccessibility != implementation.DeclaredAccessibility) + { + // PROTOTYPE(partial-properties): we should either generalize the existing diagnostic or introduce new set of diagnostics. + diagnostics.Add(ErrorCode.ERR_PartialMethodAccessibilityDifference, implementation.GetFirstLocation()); + } } private static BaseParameterListSyntax? GetParameterListSyntax(CSharpSyntaxNode syntax) => (syntax as IndexerDeclarationSyntax)?.ParameterList; + + internal bool IsPartial => (_modifiers & DeclarationModifiers.Partial) != 0; + + internal SourcePropertySymbol? OtherPartOfPartial { get => _otherPartOfPartial; } + + internal bool IsPartialDefinition => IsPartial && !AccessorsHaveImplementation && !IsExtern; + + internal bool IsPartialImplementation => IsPartial && (AccessorsHaveImplementation || IsExtern); + + internal static void InitializePartialPropertyParts(SourcePropertySymbol definition, SourcePropertySymbol implementation) + { + Debug.Assert(definition.IsPartialDefinition); + Debug.Assert(implementation.IsPartialImplementation); + + Debug.Assert(definition._otherPartOfPartial is null || definition._otherPartOfPartial == implementation); + Debug.Assert(implementation._otherPartOfPartial is null || implementation._otherPartOfPartial == definition); + + definition._otherPartOfPartial = implementation; + implementation._otherPartOfPartial = definition; + } + } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 326d8414791f1..c4476dc8709a6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -33,6 +33,7 @@ private enum Flags : byte IsAutoProperty = 1 << 1, IsExplicitInterfaceImplementation = 1 << 2, HasInitializer = 1 << 3, + AccessorsHaveImplementation = 1 << 4, } // TODO (tomat): consider splitting into multiple subclasses/rare data. @@ -81,6 +82,7 @@ protected SourcePropertySymbolBase( bool isAutoProperty, bool isExpressionBodied, bool isInitOnly, + bool accessorsHaveImplementation, RefKind refKind, string memberName, SyntaxList indexerNameAttributeLists, @@ -89,6 +91,7 @@ protected SourcePropertySymbolBase( { Debug.Assert(!isExpressionBodied || !isAutoProperty); Debug.Assert(!isExpressionBodied || !hasInitializer); + Debug.Assert(!isExpressionBodied || accessorsHaveImplementation); // PROTOTYPE(partial-properties): further adjust asserts? Debug.Assert((modifiers & DeclarationModifiers.Required) == 0 || this is SourcePropertySymbol); _syntaxRef = syntax.GetReference(); @@ -125,6 +128,11 @@ protected SourcePropertySymbolBase( _propertyFlags |= Flags.IsExpressionBodied; } + if (accessorsHaveImplementation) + { + _propertyFlags |= Flags.AccessorsHaveImplementation; + } + if (isIndexer) { if (indexerNameAttributeLists.Count == 0 || isExplicitInterfaceImplementation) @@ -479,11 +487,6 @@ public override bool IsStatic get { return (_modifiers & DeclarationModifiers.Static) != 0; } } - internal bool IsFixed - { - get { return false; } - } - /// /// Even though it is declared with an IndexerDeclarationSyntax, an explicit /// interface implementation is not an indexer because it will not cause the @@ -621,6 +624,9 @@ internal bool IsAutoPropertyWithGetAccessor protected bool IsAutoProperty => (_propertyFlags & Flags.IsAutoProperty) != 0; + protected bool AccessorsHaveImplementation + => (_propertyFlags & Flags.AccessorsHaveImplementation) != 0; + /// /// Backing field for automatically implemented property, or /// for a property with an initializer. diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs index c64029e821ca8..65bde2a1f8843 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -34,6 +34,7 @@ public SynthesizedRecordEqualityContractProperty(SourceMemberContainerTypeSymbol isAutoProperty: false, isExpressionBodied: false, isInitOnly: false, + accessorsHaveImplementation: true, RefKind.None, PropertyName, indexerNameAttributeLists: new SyntaxList(), diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index b3b268b23fe80..b276a81425b6f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -31,6 +31,7 @@ public SynthesizedRecordPropertySymbol( isAutoProperty: true, isExpressionBodied: false, isInitOnly: ShouldUseInit(containingType), + accessorsHaveImplementation: true, RefKind.None, backingParameter.Name, indexerNameAttributeLists: new SyntaxList(), diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 08aa73d9ec729..c768b5f77d786 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1652,6 +1652,36 @@ Částečná metoda {0} musí mít modifikátory přístupnosti, protože má parametry out. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Konstanta řetězce null není podporována jako vzor pro {0}. Místo toho použijte prázdný řetězec. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Modifikátor partial se může objevit jen bezprostředně před klíčovými slovy class, record, struct, interface nebo návratovým typem metody. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Modifikátor partial se může objevit jen bezprostředně před klíčovými slovy class, record, struct, interface nebo návratovým typem metody. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 7afd5f798ca1d..4fe6312e80dfe 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1652,6 +1652,36 @@ Die partielle Methode "{0}" muss Zugriffsmodifizierer aufweisen, weil sie out-Parameter verwendet. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Eine Nullkonstante der Zeichenfolge wird nicht als Muster für "{0}" unterstützt. Verwenden Sie stattdessen eine leere Zeichenfolge. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Der partial-Modifizierer kann nur unmittelbar vor "class", "record", "struct", "interface" oder einem Methodenrückgabetyp verwendet werden. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Der partial-Modifizierer kann nur unmittelbar vor "class", "record", "struct", "interface" oder einem Methodenrückgabetyp verwendet werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 333531f33ebcb..9d827b920569c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1652,6 +1652,36 @@ El método parcial "{0}" debe tener modificadores de accesibilidad porque tiene parámetros "out". + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. No se admite una constante de cadena 'null' como patrón para '{0}'. Use una cadena vacía en su lugar. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - El modificador "partial" solo puede aparecer inmediatamente antes de "class", "record", "struct", "interface" o de un tipo de valor devuelto del método. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + El modificador "partial" solo puede aparecer inmediatamente antes de "class", "record", "struct", "interface" o de un tipo de valor devuelto del método. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 5670acdf1bfd3..478d6110fb977 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1652,6 +1652,36 @@ La méthode partielle '{0}' doit avoir des modificateurs d'accessibilité, car elle a des paramètres 'out'. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Une constante « null » de chaîne n’est pas prise en charge en tant que modèle pour «{0}». Utilisez plutôt une chaîne vide. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Le modificateur 'partial' peut apparaître uniquement juste avant 'class', 'record', 'struct', 'interface' ou un type de retour de méthode. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Le modificateur 'partial' peut apparaître uniquement juste avant 'class', 'record', 'struct', 'interface' ou un type de retour de méthode. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 5ae57a81ba5f6..759401134ff68 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1652,6 +1652,36 @@ Il metodo parziale '{0}' deve contenere modificatori di accessibilità perché include parametri 'out'. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Una costante di tipo stringa 'null' non è supportata come criterio per ?{0}'. Usare invece una stringa vuota. @@ -6449,8 +6479,8 @@ target:module Compila un modulo che può essere aggiunto ad altro - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Il modificatore 'partial' può trovarsi solo immediatamente prima di 'class', 'record', 'struct', 'interface' o il tipo restituito di un metodo. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Il modificatore 'partial' può trovarsi solo immediatamente prima di 'class', 'record', 'struct', 'interface' o il tipo restituito di un metodo. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 660b612454dae..2f357d595cbcb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1652,6 +1652,36 @@ 部分メソッド '{0}' には、'out' パラメーターが指定されているため、アクセシビリティ修飾子が必要です。 + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. 文字列 'null' 定数は、'{0}' のパターンとしてサポートされていません。代わりに空の文字列を使用してください。 @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - 'partial' 修飾子は、'class'、'record'、'struct'、'interface'、またはメソッドの戻り値の型の直前にのみ指定できます。 + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + 'partial' 修飾子は、'class'、'record'、'struct'、'interface'、またはメソッドの戻り値の型の直前にのみ指定できます。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 4dcbb46c1279f..b4c91e75a44c0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1652,6 +1652,36 @@ 부분 메서드 '{0}'에는 'out' 매개 변수가 있으므로 접근성 한정자가 있어야 합니다. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. 문자열 'Null' 상수는 '{0}'에 대한 패턴으로 지원되지 않습니다. 대신 빈 문자열을 사용하세요. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - 'partial' 한정자는 'class', 'record', 'struct', 'interface' 또는 메서드 반환 형식 바로 앞에만 올 수 있습니다. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + 'partial' 한정자는 'class', 'record', 'struct', 'interface' 또는 메서드 반환 형식 바로 앞에만 올 수 있습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 15e03b63e52c2..c67ee176f20a8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1652,6 +1652,36 @@ Metoda częściowa „{0}” musi mieć modyfikatory dostępności, ponieważ ma parametry „out”. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Stała ciągu „null” nie jest obsługiwana jako wzorzec dla „{0}”. Zamiast tego użyj pustego ciągu. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Modyfikator „partial” może pojawić się tylko bezpośrednio przed słowem kluczowym „class”, „record” „struct”, „interface” lub zwracanym typem metody. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Modyfikator „partial” może pojawić się tylko bezpośrednio przed słowem kluczowym „class”, „record” „struct”, „interface” lub zwracanym typem metody. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 94452e162657f..d0104e62d8301 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1652,6 +1652,36 @@ O método parcial '{0}' precisa ter modificadores de acessibilidade porque ele tem parâmetros 'out'. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Uma constante cadeia de caracteres 'null' não é suportada como padrão para '{0}'. Use uma cadeia de caracteres vazia em seu lugar. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - O modificador 'partial' só pode aparecer imediatamente antes de 'class', de 'record', de 'struct', de 'interface' ou de um tipo de retorno de método. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + O modificador 'partial' só pode aparecer imediatamente antes de 'class', de 'record', de 'struct', de 'interface' ou de um tipo de retorno de método. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 695213d92f5f9..a7466f6994187 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1652,6 +1652,36 @@ Разделяемый метод "{0}" должен иметь модификаторы доступа, так как он содержит параметры "out". + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Строковая константа "null" не поддерживается в качестве шаблона для "{0}". Используйте вместо этого пустую строку. @@ -6450,8 +6480,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - Модификатор "partial" может использоваться только перед ключевыми словами "class", "record", "struct" и "interface" и перед возвращаемым типом метода. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + Модификатор "partial" может использоваться только перед ключевыми словами "class", "record", "struct" и "interface" и перед возвращаемым типом метода. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 721552b2359e5..0e05aca945eef 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1652,6 +1652,36 @@ '{0}' kısmi metodunun 'out' parametreleri olduğundan erişilebilirlik değiştiricileri olmalıdır. + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. Dize 'null' sabiti, '{0}' için desen olarak desteklenmiyor. Bunun yerine boş bir dize kullanın. @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - 'partial' değiştiricisi yalnızca 'class', 'record', 'struct', 'interface' ifadelerinden veya metot dönüş türünden hemen önce gelebilir. + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + 'partial' değiştiricisi yalnızca 'class', 'record', 'struct', 'interface' ifadelerinden veya metot dönüş türünden hemen önce gelebilir. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index d61012d7e3da5..13fbe2507aaaa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1652,6 +1652,36 @@ 分部方法“{0}”必须具有可访问性修饰符,因为它具有 "out" 参数。 + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. 不支持将字符串 'null' 常量作为 '{0}' 的模式。请改用空字符串。 @@ -6449,8 +6479,8 @@ - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - "partial" 修饰符的后面只能紧跟 "class"、"record"、"struct"、"interface" 或方法返回类型。 + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + "partial" 修饰符的后面只能紧跟 "class"、"record"、"struct"、"interface" 或方法返回类型。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 01ddee39364a5..31390e2de0a41 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1652,6 +1652,36 @@ 因為部分方法 '{0}' 有 'out' 參數,所以其必須有存取範圍修飾詞。 + + A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations + + + + A partial property may not have multiple implementing declarations + A partial property may not have multiple implementing declarations + + + + Property accessor '{0}' must be implemented because it is declared on the definition part + Property accessor '{0}' must be implemented because it is declared on the definition part + + + + Partial property '{0}' must have an definition part. + Partial property '{0}' must have an definition part. + + + + Partial property '{0}' must have an implementation part. + Partial property '{0}' must have an implementation part. + + + + Property accessor '{0}' does not implement any accessor declared on the definition part + Property accessor '{0}' does not implement any accessor declared on the definition part + + A string 'null' constant is not supported as a pattern for '{0}'. Use an empty string instead. 不支援字串 'null' 常數做為 '{0}' 的模式。請改為使用空字串。 @@ -6449,8 +6479,8 @@ strument:TestCoverage 產生檢測要收集 - The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. - 'partial' 修飾元只可緊接在 'class'、'record'、'struct'、'interface' 或方法傳回型別之前。 + The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. + 'partial' 修飾元只可緊接在 'class'、'record'、'struct'、'interface' 或方法傳回型別之前。 diff --git a/src/Compilers/CSharp/Test/Emit2/Diagnostics/GetDiagnosticsTests.cs b/src/Compilers/CSharp/Test/Emit2/Diagnostics/GetDiagnosticsTests.cs index 0d264412df26c..4fcd0bf2fe66c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Diagnostics/GetDiagnosticsTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Diagnostics/GetDiagnosticsTests.cs @@ -186,6 +186,8 @@ private void NonPartialMethod2() { } Assert.True(completedCompilationUnits.Contains(tree1.FilePath)); } + // PROTOTYPE(partial-properties): also test compilation events for complete and incomplete partial properties and their accessors + [Fact, WorkItem(7477, "https://github.com/dotnet/roslyn/issues/7477")] public void TestCompilationEventsForPartialMethod() { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index bf23e1d535416..6a4cc790b55c5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -10889,7 +10889,7 @@ public Test2(int x) {} Assert.False(m1.IsAsync); Assert.False(m1.IsOverride); Assert.Equal(Accessibility.Private, m1.DeclaredAccessibility); - Assert.True(m1.IsPartialMethod()); + Assert.True(m1.IsPartialMember()); Assert.Null(m1.PartialImplementationPart); var m2 = i1.GetMember("M2"); @@ -10903,7 +10903,7 @@ public Test2(int x) {} Assert.False(m2.IsAsync); Assert.False(m2.IsOverride); Assert.Equal(Accessibility.Private, m2.DeclaredAccessibility); - Assert.True(m2.IsPartialMethod()); + Assert.True(m2.IsPartialMember()); Assert.Equal(2, m2.GetAttributes().Length); Assert.Equal("Test2(1)", m2.GetAttributes()[0].ToString()); @@ -10920,7 +10920,7 @@ public Test2(int x) {} Assert.False(m2Impl.IsAsync); Assert.False(m2Impl.IsOverride); Assert.Equal(Accessibility.Private, m2Impl.DeclaredAccessibility); - Assert.True(m2Impl.IsPartialMethod()); + Assert.True(m2Impl.IsPartialMember()); Assert.Same(m2, m2Impl.PartialDefinitionPart); Assert.Equal(2, m2Impl.GetAttributes().Length); @@ -10996,7 +10996,7 @@ public Test2(int x) {} Assert.False(m1.IsAsync); Assert.False(m1.IsOverride); Assert.Equal(Accessibility.Private, m1.DeclaredAccessibility); - Assert.True(m1.IsPartialMethod()); + Assert.True(m1.IsPartialMember()); Assert.Null(m1.PartialImplementationPart); var m2 = i1.GetMember("M2"); @@ -11010,7 +11010,7 @@ public Test2(int x) {} Assert.False(m2.IsAsync); Assert.False(m2.IsOverride); Assert.Equal(Accessibility.Private, m2.DeclaredAccessibility); - Assert.True(m2.IsPartialMethod()); + Assert.True(m2.IsPartialMember()); Assert.Equal(2, m2.GetAttributes().Length); Assert.Equal("Test2(1)", m2.GetAttributes()[0].ToString()); @@ -11027,7 +11027,7 @@ public Test2(int x) {} Assert.False(m2Impl.IsAsync); Assert.False(m2Impl.IsOverride); Assert.Equal(Accessibility.Private, m2Impl.DeclaredAccessibility); - Assert.True(m2Impl.IsPartialMethod()); + Assert.True(m2Impl.IsPartialMember()); Assert.Same(m2, m2Impl.PartialDefinitionPart); Assert.Equal(2, m2Impl.GetAttributes().Length); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs new file mode 100644 index 0000000000000..b9f64efdef7ff --- /dev/null +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -0,0 +1,409 @@ +// 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 System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols +{ + public class PartialPropertiesTests : CSharpTestBase + { + [Fact] + public void MissingDeclaration_01() + { + // definition without implementation + var source = """ + partial class C + { + partial int P { get; set; } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9300: Partial property 'C.P' must have an implementation part. + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 17) + ); + + var cClass = comp.GetMember("C"); + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "System.Int32 C.P.get", + "void C.P.set", + "C..ctor()" + ], + cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString())); + } + + [Fact] + public void MissingDeclaration_02() + { + // implementation without definition + var source = """ + partial class C + { + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9301: Partial property 'C.P' must have an definition part. + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "P").WithArguments("C.P").WithLocation(3, 17) + ); + } + + [Fact] + public void DuplicateDeclaration_01() + { + // duplicate definition + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get; set; } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9300: Partial property 'C.P' must have an implementation part. + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 17), + // (4,17): error CS9302: A partial property may not have multiple defining declarations + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), + // (4,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 17) + ); + } + + [Fact] + public void DuplicateDeclaration_02() + { + // duplicate definition with single implementation + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get; set; } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9302: A partial property may not have multiple defining declarations + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), + // (4,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 17) + ); + } + + [Fact] + public void DuplicateDeclaration_03() + { + // duplicate implementation + var source = """ + partial class C + { + partial int P { get => throw null!; set { } } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9301: Partial property 'C.P' must have an definition part. + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "P").WithArguments("C.P").WithLocation(3, 17), + // (4,17): error CS9303: A partial property may not have multiple implementing declarations + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P").WithLocation(4, 17), + // (4,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 17) + ); + } + + [Fact] + public void DuplicateDeclaration_04() + { + // duplicate implementation with single definition + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get => throw null!; set { } } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (5,17): error CS9303: A partial property may not have multiple implementing declarations + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P").WithLocation(5, 17), + // (5,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(5, 17) + ); + } + + [Fact] + public void DuplicateDeclaration_05() + { + // duplicate implementation and duplicate definition + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get; set; } + partial int P { get => throw null!; set { } } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9302: A partial property may not have multiple defining declarations + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), + // (4,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get; set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 17), + // (6,17): error CS9303: A partial property may not have multiple implementing declarations + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P").WithLocation(6, 17), + // (6,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(6, 17) + ); + } + + [Fact] + public void MissingAccessor_01() + { + // implementation missing setter + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get => throw null!; } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9304: Property accessor 'C.P.set' must be implemented because it is declared on the definition part + // partial int P { get => throw null!; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.set").WithLocation(4, 17) + ); + } + + [Fact] + public void MissingAccessor_02() + { + // implementation missing getter + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get => throw null!; } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9304: Property accessor 'C.P.set' must be implemented because it is declared on the definition part + // partial int P { get => throw null!; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.set").WithLocation(4, 17) + ); + } + + [Fact] + public void MissingAccessor_03() + { + // implementation missing init + var source = """ + partial class C + { + partial int P { get; init; } + partial int P { get => throw null!; } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9304: Property accessor 'C.P.init' must be implemented because it is declared on the definition part + // partial int P { get => throw null!; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.init").WithLocation(4, 17) + ); + } + + [Fact] + public void UnexpectedAccessor_01() + { + // implementation unexpected setter + var source = """ + partial class C + { + partial int P { get; } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9305: Property accessor 'C.P.set' does not implement any accessor declared on the definition part + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyUnexpectedAccessor, "P").WithArguments("C.P.set").WithLocation(4, 17) + ); + } + + [Fact] + public void UnexpectedAccessor_02() + { + // implementation unexpected getter + var source = """ + partial class C + { + partial int P { set; } + partial int P { get => throw null!; set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9305: Property accessor 'C.P.get' does not implement any accessor declared on the definition part + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyUnexpectedAccessor, "P").WithArguments("C.P.get").WithLocation(4, 17) + ); + } + + [Fact] + public void UnexpectedAccessor_03() + { + // implementation unexpected init + var source = """ + partial class C + { + partial int P { get; } + partial int P { get => throw null!; init { } } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + // (4,17): error CS9305: Property accessor 'C.P.init' does not implement any accessor declared on the definition part + // partial int P { get => throw null!; init { } } + Diagnostic(ErrorCode.ERR_PartialPropertyUnexpectedAccessor, "P").WithArguments("C.P.init").WithLocation(4, 17) + ); + } + + [Fact] + public void Semantics_01() + { + // happy definition + implementation case + var source = """ + using System; + + var c = new C { P = 1 }; + Console.Write(c.P); + + partial class C + { + public partial int P { get; set; } + } + + partial class C + { + private int _p; + public partial int P { get => _p; set => _p = value; } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.P.get", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld "int C._p" + IL_0006: ret + } + """); + verifier.VerifyIL("C.P.set", """ + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld "int C._p" + IL_0007: ret + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var cClass = comp.GetMember("C"); + var members = cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "System.Int32 C.P.get", + "void C.P.set", + "System.Int32 C._p", + "C..ctor()" + ], members); + } + + [Fact] + public void ModifierDifference_01() + { + // access modifier on declaration but not implementation + var source = """ + partial class C + { + public partial int P { get; set; } + } + + partial class C + { + partial int P { get => throw null!; set { } } + } + """; + + // PROTOTYPE(partial-properties): diagnostic message should be generalized for properties as well. + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (8,17): error CS8799: Both partial method declarations must have identical accessibility modifiers. + // partial int P { get => throw null!; set { } } + Diagnostic(ErrorCode.ERR_PartialMethodAccessibilityDifference, "P").WithLocation(8, 17)); + } + + [Fact] + public void ModifierDifference_02() + { + // access modifier on declaration but not implementation + var source = """ + partial class C + { + public partial int P { get; private set; } + } + + partial class C + { + public partial int P { get => throw null!; set { } } + } + """; + + var comp = CreateCompilation(source); + + // PROTOTYPE(partial-properties): missing diagnostic + comp.VerifyEmitDiagnostics(); + } + + // PROTOTYPE(partial-properties): test more mismatching scenarios + // PROTOTYPE(partial-properties): test indexers incl parameters with attributes + // PROTOTYPE(partial-properties): test merging property attributes + } +} diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index 99fb35186708f..e6ebc45539955 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -14084,15 +14084,15 @@ partial int this[int index] "; var comp = CreateCompilation(text); comp.VerifyDiagnostics( - // (4,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // (4,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method or property return type. // partial int f; Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(4, 5), - // (5,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + // (5,20): error CS9301: Partial property 'C.P' must have an definition part. // partial object P { get { return null; } } - Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(5, 5), - // (6,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. + Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "P").WithArguments("C.P").WithLocation(5, 20), + // (6,17): error CS9301: Partial property 'C.this[int]' must have an definition part. // partial int this[int index] - Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(6, 5), + Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "this").WithArguments("C.this[int]").WithLocation(6, 17), // (4,17): warning CS0169: The field 'C.f' is never used // partial int f; Diagnostic(ErrorCode.WRN_UnreferencedField, "f").WithArguments("C.f").WithLocation(4, 17)); From 36cbfb0380e3035c10e318b4dd2693f14c7b9742 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 24 Apr 2024 16:41:17 -0700 Subject: [PATCH 2/6] Address feedback --- .../BinderFactory.BinderFactoryVisitor.cs | 4 +- .../Source/SourceMemberContainerSymbol.cs | 30 ++++------ .../Source/SourcePropertyAccessorSymbol.cs | 8 +++ .../Symbols/Source/SourcePropertySymbol.cs | 9 ++- .../Symbol/Symbols/PartialPropertiesTests.cs | 58 +++++++++++++++++++ 5 files changed, 85 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 770f45d21f017..2ffc59a29d711 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -288,6 +288,7 @@ public override Binder VisitAccessorDeclaration(AccessorDeclarationSyntax parent if ((object)propertySymbol != null) { accessor = (parent.Kind() == SyntaxKind.GetAccessorDeclaration) ? propertySymbol.GetMethod : propertySymbol.SetMethod; + // PROTOTYPE(partial-properties): check if a property with no accessors could fail this assertion, in which case we should either adjust the assertion or remove it. Debug.Assert(accessor is not null || parent.HasErrors); } break; @@ -616,8 +617,7 @@ bool checkSymbol(Symbol sym, TextSpan memberSpan, SymbolKind kind, out Symbol re // If this is a partial property, the property represents the defining part, // not the implementation (property.Locations includes both parts). If the // span is in fact in the implementation, return that property instead. - var property = (SourcePropertySymbol)sym; - var implementation = property.IsPartialDefinition ? property.OtherPartOfPartial : property; + var implementation = ((SourcePropertySymbol)sym).PartialImplementationPart; if ((object)implementation != null) { if (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan)) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index b31d9b5ec2640..e4e7619fbe3b7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3592,22 +3592,21 @@ private void MergePartialMembers( continue; } - Debug.Assert(symbol.GetType() == prev.GetType()); switch (symbol, prev) { case (SourceOrdinaryMethodSymbol currentMethod, SourceOrdinaryMethodSymbol prevMethod): - mergePartialMethods(ref membersByName, name, currentMethod, prevMethod); + mergePartialMethods(ref membersByName, name, currentMethod, prevMethod, diagnostics); break; case (SourcePropertySymbol currentProperty, SourcePropertySymbol prevProperty): - mergePartialProperties(ref membersByName, name, currentProperty, prevProperty); + mergePartialProperties(ref membersByName, name, currentProperty, prevProperty, diagnostics); break; case (SourcePropertyAccessorSymbol, SourcePropertyAccessorSymbol): break; // accessor symbols and their diagnostics are handled by processing the associated property default: - throw ExceptionUtilities.UnexpectedValue(prev); + throw ExceptionUtilities.UnexpectedValue((symbol, prev)); } } @@ -3648,7 +3647,7 @@ private void MergePartialMembers( memberNames.Free(); - void mergePartialMethods(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourceOrdinaryMethodSymbol currentMethod, SourceOrdinaryMethodSymbol prevMethod) + void mergePartialMethods(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourceOrdinaryMethodSymbol currentMethod, SourceOrdinaryMethodSymbol prevMethod, BindingDiagnosticBag diagnostics) { if (currentMethod.IsPartialImplementation && (prevMethod.IsPartialImplementation || (prevMethod.OtherPartOfPartial is MethodSymbol otherImplementation && (object)otherImplementation != currentMethod))) @@ -3657,7 +3656,7 @@ void mergePartialMethods(ref Dictionary, ImmutableArray, ImmutableArray, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertySymbol currentProperty, SourcePropertySymbol prevProperty) + void mergePartialProperties(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertySymbol currentProperty, SourcePropertySymbol prevProperty, BindingDiagnosticBag diagnostics) { if (currentProperty.IsPartialImplementation && (prevProperty.IsPartialImplementation || (prevProperty.OtherPartOfPartial is SourcePropertySymbol otherImplementation && (object)otherImplementation != currentProperty))) @@ -3682,7 +3681,7 @@ void mergePartialProperties(ref Dictionary, ImmutableArray< diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateImplementation, currentProperty.GetFirstLocation()); } else if (currentProperty.IsPartialDefinition && - (prevProperty.IsPartialDefinition || (prevProperty.OtherPartOfPartial is SourcePropertySymbol otherDefinition && (object)otherDefinition != currentProperty))) + (prevProperty.IsPartialDefinition || (prevProperty.OtherPartOfPartial is SourcePropertySymbol otherDefinition && (object)otherDefinition != currentProperty))) { diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateDefinition, currentProperty.GetFirstLocation()); } @@ -3731,21 +3730,14 @@ void mergeAccessors(ref Dictionary, ImmutableArray> } else { - var (definitionAccessor, implementationAccessor) = currentProperty.IsPartialDefinition ? (currentAccessor, prevAccessor) : (prevAccessor, currentAccessor); + var implementationAccessor = currentProperty.IsPartialDefinition ? prevAccessor : currentAccessor; membersByName[name] = Remove(membersByName[name], implementationAccessor); } } } } - /// - /// Fix up a partial method by combining its defining and implementing declarations, updating the array of symbols (by name), - /// and returning the combined symbol. - /// - /// The symbols array containing both the latent and implementing declaration - /// One of the two declarations - /// The other declaration - /// An updated symbols array containing only one method symbol representing the two parts + /// Links together the definition and implementation parts of a partial method. Returns a member list which has the implementation part removed. private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourceOrdinaryMethodSymbol part1, SourceOrdinaryMethodSymbol part2) { SourceOrdinaryMethodSymbol definition; @@ -3767,7 +3759,7 @@ private static ImmutableArray FixPartialMember(ImmutableArray sy return Remove(symbols, implementation); } - // PROTOTYPE(partial-properties): is there some abstraction that would make this nice? + /// Links together the definition and implementation parts of a partial property. Returns a member list which has the implementation part removed. private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourcePropertySymbol part1, SourcePropertySymbol part2) { SourcePropertySymbol definition; @@ -3785,7 +3777,7 @@ private static ImmutableArray FixPartialMember(ImmutableArray sy SourcePropertySymbol.InitializePartialPropertyParts(definition, implementation); - // a partial method is represented in the member list by its definition part: + // a partial property is represented in the member list by its definition part: return Remove(symbols, implementation); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index 6a1bf22c05010..1dc0587d2ded9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -819,5 +819,13 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r internal bool IsPartialDefinition => _property is SourcePropertySymbol { IsPartialDefinition: true }; internal bool IsPartialImplementation => _property is SourcePropertySymbol { IsPartialImplementation: true }; + + public sealed override bool IsExtern + { + get + { + return PartialImplementationPart is { } implementation ? implementation.IsExtern : base.IsExtern; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 08356a3534ad6..d5611bed4c145 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -58,7 +58,6 @@ private static SourcePropertySymbol Create( diagnostics, out _); - // PROTOTYPE(partial-properties): it's unclear if the subtle difference between former 'isAutoProperty' and '!accessorsHaveImplementation' matters bool isAutoProperty = (modifiers & DeclarationModifiers.Partial) == 0 && !accessorsHaveImplementation; bool isExpressionBodied = !hasAccessorList && GetArrowExpression(syntax) != null; @@ -234,6 +233,7 @@ private static void GetAccessorDeclarations( else { accessorsHaveImplementation = GetArrowExpression(syntax) is object; + Debug.Assert(accessorsHaveImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression. } } @@ -582,12 +582,16 @@ private void PartialPropertyChecks(SourcePropertySymbol implementation, BindingD internal bool IsPartial => (_modifiers & DeclarationModifiers.Partial) != 0; - internal SourcePropertySymbol? OtherPartOfPartial { get => _otherPartOfPartial; } + internal SourcePropertySymbol? OtherPartOfPartial => _otherPartOfPartial; internal bool IsPartialDefinition => IsPartial && !AccessorsHaveImplementation && !IsExtern; internal bool IsPartialImplementation => IsPartial && (AccessorsHaveImplementation || IsExtern); + internal SourcePropertySymbol? PartialDefinitionPart => IsPartialImplementation ? OtherPartOfPartial : null; + + internal SourcePropertySymbol? PartialImplementationPart => IsPartialDefinition ? OtherPartOfPartial : null; + internal static void InitializePartialPropertyParts(SourcePropertySymbol definition, SourcePropertySymbol implementation) { Debug.Assert(definition.IsPartialDefinition); @@ -599,6 +603,5 @@ internal static void InitializePartialPropertyParts(SourcePropertySymbol definit definition._otherPartOfPartial = implementation; implementation._otherPartOfPartial = definition; } - } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index b9f64efdef7ff..eeafbfa63bccd 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -301,6 +301,64 @@ partial class C ); } + [Fact] + public void AccessorKind_01() + { + // set vs init + var source = """ + partial class C + { + partial int P { get; set; } + partial int P { get => throw null!; init { } } + } + """; + // PROTOTYPE(partial-properties): give an error diagnostic for an accessor kind difference + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + ); + } + + [Fact] + public void Extern_01() + { + // PROTOTYPE(partial-properties): test that appropriate flags are set in metadata for the property accessors. + // See ExtendedPartialMethodsTests.Extern_Symbols as a starting point. + var source = """ + partial class C + { + partial int P { get; set; } + extern partial int P { get; set; } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + ); + } + + [Fact] + public void Extern_02() + { + var source = """ + partial class C + { + extern partial int P { get; set; } + extern partial int P { get; set; } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + // (3,24): error CS9301: Partial property 'C.P' must have an definition part. + // extern partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "P").WithArguments("C.P").WithLocation(3, 24), + // (4,24): error CS9303: A partial property may not have multiple implementing declarations + // extern partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P").WithLocation(4, 24), + // (4,24): error CS0102: The type 'C' already contains a definition for 'P' + // extern partial int P { get; set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 24) + ); + } + [Fact] public void Semantics_01() { From 038e6d3362f8d938376bc821ffc5fe00f9f956d0 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 26 Apr 2024 11:59:05 -0700 Subject: [PATCH 3/6] Address feedback --- .../Symbol/Symbols/PartialPropertiesTests.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index eeafbfa63bccd..b664c7b83a0a6 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -304,7 +304,7 @@ partial class C [Fact] public void AccessorKind_01() { - // set vs init + // definition has set but implementation has init var source = """ partial class C { @@ -318,6 +318,23 @@ partial class C ); } + [Fact] + public void AccessorKind_02() + { + // definition has init but implementation has set + var source = """ + partial class C + { + partial int P { get; init; } + partial int P { get => throw null!; set { } } + } + """; + // PROTOTYPE(partial-properties): give an error diagnostic for an accessor kind difference + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + ); + } + [Fact] public void Extern_01() { @@ -333,6 +350,11 @@ partial class C var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); comp.VerifyEmitDiagnostics( ); + + var prop = comp.GetMember("C.P"); + // PROTOTYPE(partial-properties): a partial method definition should delegate to its implementation part to implement this API, i.e. return 'true' here + Assert.False(prop.GetPublicSymbol().IsExtern); + Assert.True(prop.PartialImplementationPart!.GetPublicSymbol().IsExtern); } [Fact] From bc5f89f93c0942c9f3f8804a295e0b8defe07378 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 29 Apr 2024 16:23:29 -0700 Subject: [PATCH 4/6] adjust InitializePartialMethod/PropertyParts asserts to deal with a potential race --- .../Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs | 7 +++++-- .../CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs index c84f0bb0098d2..af6a7b0362d14 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs @@ -1000,11 +1000,14 @@ internal static void InitializePartialMethodParts(SourceOrdinaryMethodSymbolComp Debug.Assert(definition.IsPartialDefinition); Debug.Assert(implementation.IsPartialImplementation); - Debug.Assert(definition._otherPartOfPartial is null || definition._otherPartOfPartial == implementation); - Debug.Assert(implementation._otherPartOfPartial is null || implementation._otherPartOfPartial == definition); + Debug.Assert(definition._otherPartOfPartial is not { } alreadySetImplPart || alreadySetImplPart == implementation); + Debug.Assert(implementation._otherPartOfPartial is not { } alreadySetDefPart || alreadySetDefPart == definition); definition._otherPartOfPartial = implementation; implementation._otherPartOfPartial = definition; + + Debug.Assert(definition._otherPartOfPartial == implementation); + Debug.Assert(implementation._otherPartOfPartial == definition); } protected sealed override MethodSymbol FindExplicitlyImplementedMethod(BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index d5611bed4c145..97901dc02bf0c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -597,11 +597,14 @@ internal static void InitializePartialPropertyParts(SourcePropertySymbol definit Debug.Assert(definition.IsPartialDefinition); Debug.Assert(implementation.IsPartialImplementation); - Debug.Assert(definition._otherPartOfPartial is null || definition._otherPartOfPartial == implementation); - Debug.Assert(implementation._otherPartOfPartial is null || implementation._otherPartOfPartial == definition); + Debug.Assert(definition._otherPartOfPartial is not { } alreadySetImplPart || alreadySetImplPart == implementation); + Debug.Assert(implementation._otherPartOfPartial is not { } alreadySetDefPart || alreadySetDefPart == definition); definition._otherPartOfPartial = implementation; implementation._otherPartOfPartial = definition; + + Debug.Assert(definition._otherPartOfPartial == implementation); + Debug.Assert(implementation._otherPartOfPartial == definition); } } } From 1f1a7a0217e0db8f667252beb8bd5f0fec2bfb35 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 29 Apr 2024 17:00:34 -0700 Subject: [PATCH 5/6] Address impl feedback --- .../BinderFactory.BinderFactoryVisitor.cs | 33 +++++-------------- .../CSharp/Portable/CSharpResources.resx | 2 +- .../Source/SourceMemberContainerSymbol.cs | 27 +++++++-------- .../Source/SourcePropertyAccessorSymbol.cs | 8 +---- .../Portable/xlf/CSharpResources.cs.xlf | 4 +-- .../Portable/xlf/CSharpResources.de.xlf | 4 +-- .../Portable/xlf/CSharpResources.es.xlf | 4 +-- .../Portable/xlf/CSharpResources.fr.xlf | 4 +-- .../Portable/xlf/CSharpResources.it.xlf | 4 +-- .../Portable/xlf/CSharpResources.ja.xlf | 4 +-- .../Portable/xlf/CSharpResources.ko.xlf | 4 +-- .../Portable/xlf/CSharpResources.pl.xlf | 4 +-- .../Portable/xlf/CSharpResources.pt-BR.xlf | 4 +-- .../Portable/xlf/CSharpResources.ru.xlf | 4 +-- .../Portable/xlf/CSharpResources.tr.xlf | 4 +-- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 4 +-- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 4 +-- .../Symbol/Symbols/PartialPropertiesTests.cs | 6 ++-- 18 files changed, 51 insertions(+), 77 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 2ffc59a29d711..0bd1540dc6984 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -587,38 +587,23 @@ bool checkSymbol(Symbol sym, TextSpan memberSpan, SymbolKind kind, out Symbol re return false; } - if (sym.Kind == SymbolKind.Method) + if (kind is SymbolKind.Method or SymbolKind.Property) { if (InSpan(sym.GetFirstLocation(), this.syntaxTree, memberSpan)) { return true; } - // If this is a partial method, the method represents the defining part, - // not the implementation (method.Locations includes both parts). If the - // span is in fact in the implementation, return that method instead. - var implementation = ((MethodSymbol)sym).PartialImplementationPart; - if ((object)implementation != null) - { - if (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan)) + // 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 { - result = implementation; - return true; + MethodSymbol method => (Symbol)method.PartialImplementationPart, + SourcePropertySymbol property => property.PartialImplementationPart, + _ => throw ExceptionUtilities.UnexpectedValue(sym) } - } - } - else if (sym.Kind == SymbolKind.Property) - { - if (InSpan(sym.GetFirstLocation(), this.syntaxTree, memberSpan)) - { - return true; - } - - // If this is a partial property, the property represents the defining part, - // not the implementation (property.Locations includes both parts). If the - // span is in fact in the implementation, return that property instead. - var implementation = ((SourcePropertySymbol)sym).PartialImplementationPart; - if ((object)implementation != null) + is { } implementation) { if (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan)) { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index dd9894980cfb4..a0e8a5efe68a8 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7936,7 +7936,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Partial property '{0}' must have an definition part. - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. A partial property may not have multiple implementing declarations diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index e4e7619fbe3b7..eb8de05229cf1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3637,7 +3637,7 @@ private void MergePartialMembers( break; case SourcePropertyAccessorSymbol: - break; // handled by SourcePropertySymbol case + break; // diagnostics for missing partial accessors are handled in 'mergePartialProperties'. default: throw ExceptionUtilities.UnexpectedValue(symbol); @@ -3713,25 +3713,20 @@ void mergePartialProperties(ref Dictionary, ImmutableArray< void mergeAccessors(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertyAccessorSymbol? currentAccessor, SourcePropertyAccessorSymbol? prevAccessor) { Debug.Assert(currentAccessor != null || prevAccessor != null); - - // When an accessor is present on definition but not on implementation, the accessor is said to be missing on the implementation. - // When an accessor is present on implementation but not on definition, the accessor is said to be unexpected on the implementation. - if (currentAccessor == null) - { - // Partial definition is the source of truth for which accessors should be present. - var (errorCode, propertyToBlame) = prevProperty.IsPartialDefinition ? (ErrorCode.ERR_PartialPropertyMissingAccessor, currentProperty) : (ErrorCode.ERR_PartialPropertyUnexpectedAccessor, prevProperty); - diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), prevAccessor!); - } - else if (prevAccessor == null) + if (currentAccessor != null && prevAccessor != null) { - // Partial definition is the source of truth for which accessors should be present. - var (errorCode, propertyToBlame) = currentProperty.IsPartialDefinition ? (ErrorCode.ERR_PartialPropertyMissingAccessor, prevProperty) : (ErrorCode.ERR_PartialPropertyUnexpectedAccessor, currentProperty); - diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), currentAccessor!); + var implementationAccessor = currentProperty.IsPartialDefinition ? prevAccessor : currentAccessor; + membersByName[name] = Remove(membersByName[name], implementationAccessor); } else { - var implementationAccessor = currentProperty.IsPartialDefinition ? prevAccessor : currentAccessor; - membersByName[name] = Remove(membersByName[name], implementationAccessor); + var (foundAccessor, containingProperty, otherProperty) = prevAccessor != null ? (prevAccessor, prevProperty, currentProperty) : (currentAccessor!, currentProperty, prevProperty); + // When an accessor is present on definition but not on implementation, the accessor is said to be missing on the implementation. + // When an accessor is present on implementation but not on definition, the accessor is said to be unexpected on the implementation. + var (errorCode, propertyToBlame) = foundAccessor.IsPartialDefinition + ? (ErrorCode.ERR_PartialPropertyMissingAccessor, otherProperty) + : (ErrorCode.ERR_PartialPropertyUnexpectedAccessor, containingProperty); + diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), foundAccessor); } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index 1dc0587d2ded9..4951afc17644a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -820,12 +820,6 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r internal bool IsPartialDefinition => _property is SourcePropertySymbol { IsPartialDefinition: true }; internal bool IsPartialImplementation => _property is SourcePropertySymbol { IsPartialImplementation: true }; - public sealed override bool IsExtern - { - get - { - return PartialImplementationPart is { } implementation ? implementation.IsExtern : base.IsExtern; - } - } + public sealed override bool IsExtern => PartialImplementationPart is { } implementation ? implementation.IsExtern : base.IsExtern; } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index c768b5f77d786..e8753eb5a0e22 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 4fe6312e80dfe..cac3806ebb9ec 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 9d827b920569c..0089a872a5169 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 478d6110fb977..7cb80db35ee47 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 759401134ff68..09889c26f1d40 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 2f357d595cbcb..c4e173d6aefdc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index b4c91e75a44c0..d395accd59824 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c67ee176f20a8..ac22c57d7bc81 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index d0104e62d8301..3c479eaebb653 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index a7466f6994187..aae6497773b82 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 0e05aca945eef..ede74a2ac3db5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 13fbe2507aaaa..4a44bf7c06126 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 31390e2de0a41..bcaf4338f524f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1653,8 +1653,8 @@ - A partial property may not have multiple defining declarations - A partial property may not have multiple defining declarations + A partial property may not have multiple defining declarations, and cannot be an auto-property. + A partial property may not have multiple defining declarations, and cannot be an auto-property. diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index b664c7b83a0a6..615f4299c79d1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -77,7 +77,7 @@ partial class C // (3,17): error CS9300: Partial property 'C.P' must have an implementation part. // partial int P { get; set; } Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 17), - // (4,17): error CS9302: A partial property may not have multiple defining declarations + // (4,17): error CS9302: A partial property may not have multiple defining declarations, and cannot be an auto-property. // partial int P { get; set; } Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), // (4,17): error CS0102: The type 'C' already contains a definition for 'P' @@ -100,7 +100,7 @@ partial class C """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (4,17): error CS9302: A partial property may not have multiple defining declarations + // (4,17): error CS9302: A partial property may not have multiple defining declarations, and cannot be an auto-property. // partial int P { get; set; } Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), // (4,17): error CS0102: The type 'C' already contains a definition for 'P' @@ -172,7 +172,7 @@ partial class C """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (4,17): error CS9302: A partial property may not have multiple defining declarations + // (4,17): error CS9302: A partial property may not have multiple defining declarations, and cannot be an auto-property. // partial int P { get; set; } Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), // (4,17): error CS0102: The type 'C' already contains a definition for 'P' From 95cf0a35f8c55ea4074e237d6550ef7102cac0dc Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 29 Apr 2024 19:00:20 -0700 Subject: [PATCH 6/6] Address more feedback --- .../BinderFactory.BinderFactoryVisitor.cs | 2 + .../Source/SourceMemberContainerSymbol.cs | 6 +- .../Symbols/Source/SourcePropertySymbol.cs | 1 + .../Symbol/Symbols/PartialPropertiesTests.cs | 563 +++++++++++++++++- 4 files changed, 549 insertions(+), 23 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 0bd1540dc6984..24d7b988defbc 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -598,11 +598,13 @@ bool checkSymbol(Symbol sym, TextSpan memberSpan, SymbolKind kind, out Symbol re // 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 (InSpan(implementation.GetFirstLocation(), this.syntaxTree, memberSpan)) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index eb8de05229cf1..40e7f04cc0ebf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3606,7 +3606,11 @@ private void MergePartialMembers( break; // accessor symbols and their diagnostics are handled by processing the associated property default: - throw ExceptionUtilities.UnexpectedValue((symbol, prev)); + // This is an error scenario. We simply don't merge the symbols in this case and a duplicate name diagnostic is reported separately. + // One way this case can be reached is if type contains both `public partial int P { get; }` and `public partial int P_get();`. + Debug.Assert(symbol is SourceOrdinaryMethodSymbol or SourcePropertySymbol or SourcePropertyAccessorSymbol); + Debug.Assert(prev is SourceOrdinaryMethodSymbol or SourcePropertySymbol or SourcePropertyAccessorSymbol); + break; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 97901dc02bf0c..23dbfcd9070d6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SourcePropertySymbol : SourcePropertySymbolBase { + // PROTOTYPE(partial-properties): Verify that the increase in memory consumption from this is acceptable private SourcePropertySymbol? _otherPartOfPartial; internal static SourcePropertySymbol Create(SourceMemberContainerTypeSymbol containingType, Binder bodyBinder, PropertyDeclarationSyntax syntax, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index 615f4299c79d1..701017988a168 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -9,24 +9,31 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols { public class PartialPropertiesTests : CSharpTestBase { - [Fact] - public void MissingDeclaration_01() + [Theory] + [InlineData("partial int P { get; set; }")] + [InlineData("partial int P { get; }")] + [InlineData("partial int P { set; }")] + [InlineData("partial int P { get; init; }")] + [InlineData("partial int P { init; }")] + public void MissingDeclaration_01(string definitionPart) { // definition without implementation - var source = """ + var source = $$""" partial class C { - partial int P { get; set; } + {{definitionPart}} } """; - var comp = CreateCompilation(source); + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); comp.VerifyEmitDiagnostics( // (3,17): error CS9300: Partial property 'C.P' must have an implementation part. // partial int P { get; set; } @@ -34,31 +41,125 @@ partial class C ); var cClass = comp.GetMember("C"); - AssertEx.Equal([ - "System.Int32 C.P { get; set; }", - "System.Int32 C.P.get", - "void C.P.set", - "C..ctor()" - ], - cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString())); + var prop = cClass.GetMember("P"); + Assert.True(prop.IsPartialDefinition); + Assert.Null(prop.PartialImplementationPart); + + var members = cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + switch (definitionPart) + { + case "partial int P { get; set; }": + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "System.Int32 C.P.get", + "void C.P.set", + "C..ctor()" + ], members); + break; + case "partial int P { get; }": + AssertEx.Equal([ + "System.Int32 C.P { get; }", + "System.Int32 C.P.get", + "C..ctor()" + ], members); + break; + case "partial int P { set; }": + AssertEx.Equal([ + "System.Int32 C.P { set; }", + "void C.P.set", + "C..ctor()" + ], members); + break; + case "partial int P { get; init; }": + AssertEx.Equal([ + "System.Int32 C.P { get; init; }", + "System.Int32 C.P.get", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.P.init", + "C..ctor()" + ], members); + break; + case "partial int P { init; }": + AssertEx.Equal([ + "System.Int32 C.P { init; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.P.init", + "C..ctor()" + ], members); + break; + default: + throw ExceptionUtilities.Unreachable(); + } } - [Fact] - public void MissingDeclaration_02() + [Theory] + [InlineData("partial int P { get => throw null!; set { } }")] + [InlineData("partial int P { get => throw null!; }")] + [InlineData("partial int P { set { } }")] + [InlineData("partial int P { get => throw null!; init { } }")] + [InlineData("partial int P { init { } }")] + public void MissingDeclaration_02(string implementationPart) { // implementation without definition - var source = """ + var source = $$""" partial class C { - partial int P { get => throw null!; set { } } + {{implementationPart}} } """; - var comp = CreateCompilation(source); + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); comp.VerifyEmitDiagnostics( // (3,17): error CS9301: Partial property 'C.P' must have an definition part. // partial int P { get => throw null!; set { } } Diagnostic(ErrorCode.ERR_PartialPropertyMissingDefinition, "P").WithArguments("C.P").WithLocation(3, 17) ); + + var cClass = comp.GetMember("C"); + var prop = cClass.GetMember("P"); + Assert.True(prop.IsPartialImplementation); + Assert.Null(prop.PartialDefinitionPart); + + var members = cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + switch (implementationPart) + { + case "partial int P { get => throw null!; set { } }": + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "System.Int32 C.P.get", + "void C.P.set", + "C..ctor()" + ], members); + break; + case "partial int P { get => throw null!; }": + AssertEx.Equal([ + "System.Int32 C.P { get; }", + "System.Int32 C.P.get", + "C..ctor()" + ], members); + break; + case "partial int P { set { } }": + AssertEx.Equal([ + "System.Int32 C.P { set; }", + "void C.P.set", + "C..ctor()" + ], members); + break; + case "partial int P { get => throw null!; init { } }": + AssertEx.Equal([ + "System.Int32 C.P { get; init; }", + "System.Int32 C.P.get", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.P.init", + "C..ctor()" + ], members); + break; + case "partial int P { init { } }": + AssertEx.Equal([ + "System.Int32 C.P { init; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.P.init", + "C..ctor()" + ], members); + break; + default: + throw ExceptionUtilities.Unreachable(); + } } [Fact] @@ -187,6 +288,90 @@ partial class C ); } + [Fact] + public void DuplicateDeclaration_06() + { + // partial method and partial property have the same name + var source = """ + partial class C + { + public partial int P { get; set; } + public partial int P() => 1; + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,24): error CS9300: Partial property 'C.P' must have an implementation part. + // public partial int P { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 24), + // (4,24): error CS0759: No defining declaration found for implementing declaration of partial method 'C.P()' + // public partial int P() => 1; + Diagnostic(ErrorCode.ERR_PartialMethodMustHaveLatent, "P").WithArguments("C.P()").WithLocation(4, 24), + // (4,24): error CS0102: The type 'C' already contains a definition for 'P' + // public partial int P() => 1; + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 24) + ); + } + + [Fact] + public void DuplicateDeclaration_07() + { + // partial method and partial property accessor have the same metadata name + var source = """ + partial class C + { + public partial int P { get; } + public partial int get_P() => 1; + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,24): error CS9300: Partial property 'C.P' must have an implementation part. + // public partial int P { get; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 24), + // (3,28): error CS0082: Type 'C' already reserves a member called 'get_P' with the same parameter types + // public partial int P { get; } + Diagnostic(ErrorCode.ERR_MemberReserved, "get").WithArguments("get_P", "C").WithLocation(3, 28) + ); + } + + [Fact] + public void DuplicateDeclaration_08() + { + // multiple implementing declarations where accessors are "split" across declarations + var source = """ + partial class C + { + public partial int P { get; set; } + public partial int P { get => 1; } + public partial int P { set { } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,24): error CS9304: Property accessor 'C.P.set' must be implemented because it is declared on the definition part + // public partial int P { get => 1; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.set").WithLocation(4, 24), + // (5,24): error CS9303: A partial property may not have multiple implementing declarations + // public partial int P { set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P").WithLocation(5, 24), + // (5,24): error CS0102: The type 'C' already contains a definition for 'P' + // public partial int P { set { } } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(5, 24) + ); + + if (comp.GetMembers("C.P") is not [SourcePropertySymbol prop, SourcePropertySymbol duplicateProp]) + throw ExceptionUtilities.UnexpectedValue(comp.GetMembers("C.P")); + + Assert.True(prop.IsPartialDefinition); + Assert.Equal("System.Int32 C.P { get; set; }", prop.ToTestDisplayString()); + Assert.Equal("System.Int32 C.P { get; }", prop.PartialImplementationPart.ToTestDisplayString()); + + Assert.True(duplicateProp.IsPartialImplementation); + Assert.Null(duplicateProp.PartialDefinitionPart); + Assert.Equal("System.Int32 C.P { set; }", duplicateProp.ToTestDisplayString()); + } + [Fact] public void MissingAccessor_01() { @@ -214,14 +399,14 @@ public void MissingAccessor_02() partial class C { partial int P { get; set; } - partial int P { get => throw null!; } + partial int P { set { } } } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (4,17): error CS9304: Property accessor 'C.P.set' must be implemented because it is declared on the definition part - // partial int P { get => throw null!; } - Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.set").WithLocation(4, 17) + // (4,17): error CS9304: Property accessor 'C.P.get' must be implemented because it is declared on the definition part + // partial int P { set { } } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingAccessor, "P").WithArguments("C.P.get").WithLocation(4, 17) ); } @@ -244,6 +429,62 @@ partial class C ); } + [Theory] + [InlineData("get")] + [InlineData("set")] + [InlineData("init")] + public void MissingAccessor_04(string accessorKind) + { + // duplicate property definitions, one with a single accessor, one empty + var source = $$""" + partial class C + { + partial int P { {{accessorKind}}; } + partial int P { } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9300: Partial property 'C.P' must have an implementation part. + // partial int P { {{accessorKind}}; } + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P").WithArguments("C.P").WithLocation(3, 17), + // (4,17): error CS9302: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial int P { } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P").WithLocation(4, 17), + // (4,17): error CS0102: The type 'C' already contains a definition for 'P' + // partial int P { } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P").WithArguments("C", "P").WithLocation(4, 17), + // (4,17): error CS0548: 'C.P': property or indexer must have at least one accessor + // partial int P { } + Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P").WithArguments("C.P").WithLocation(4, 17) + ); + } + + [Theory] + [InlineData("get")] + [InlineData("set")] + [InlineData("init")] + public void MissingAccessor_05(string accessorKind) + { + // implementation single accessor, definition empty + var source = $$""" + partial class C + { + partial int P { {{accessorKind}} => throw null!; } + partial int P { } + } + """; + var comp = CreateCompilation([source, IsExternalInitTypeDefinition]); + comp.VerifyEmitDiagnostics( + // (3,17): error CS9305: Property accessor 'C.P.{accessorKind}' does not implement any accessor declared on the definition part + // partial int P { {{accessorKind}} => throw null!; } + Diagnostic(ErrorCode.ERR_PartialPropertyUnexpectedAccessor, "P").WithArguments($"C.P.{accessorKind}").WithLocation(3, 17), + // (4,17): error CS0548: 'C.P': property or indexer must have at least one accessor + // partial int P { } + Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P").WithArguments("C.P").WithLocation(4, 17) + ); + } + [Fact] public void UnexpectedAccessor_01() { @@ -434,6 +675,283 @@ .maxstack 2 "System.Int32 C._p", "C..ctor()" ], members); + + var propDefinition = comp.GetMember("C.P"); + Assert.True(propDefinition.IsPartialDefinition); + + var propImplementation = propDefinition.PartialImplementationPart!; + Assert.True(propImplementation.IsPartialImplementation); + + Assert.Same(propDefinition, propImplementation.PartialDefinitionPart); + Assert.Null(propImplementation.PartialImplementationPart); + Assert.Same(propImplementation, propDefinition.PartialImplementationPart); + Assert.Null(propDefinition.PartialDefinitionPart); + + Assert.Same(propDefinition.GetMethod, comp.GetMember("C.get_P")); + Assert.Same(propDefinition.SetMethod, comp.GetMember("C.set_P")); + + verifyAccessor(propDefinition.GetMethod!, propImplementation.GetMethod!); + verifyAccessor(propDefinition.SetMethod!, propImplementation.SetMethod!); + + void verifyAccessor(MethodSymbol definitionAccessor, MethodSymbol implementationAccessor) + { + Assert.True(definitionAccessor.IsPartialDefinition()); + Assert.True(implementationAccessor.IsPartialImplementation()); + + Assert.Same(implementationAccessor, definitionAccessor.PartialImplementationPart); + Assert.Null(definitionAccessor.PartialDefinitionPart); + Assert.Same(definitionAccessor, implementationAccessor.PartialDefinitionPart); + Assert.Null(implementationAccessor.PartialImplementationPart); + } + } + + [Theory] + [InlineData("public partial int P { get => _p; }")] + [InlineData("public partial int P => _p;")] + public void Semantics_02(string implementationPart) + { + // get-only + var source = $$""" + using System; + + var c = new C(); + Console.Write(c.P); + + partial class C + { + public partial int P { get; } + } + + partial class C + { + private int _p = 1; + {{implementationPart}} + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.P.get", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld "int C._p" + IL_0006: ret + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var cClass = comp.GetMember("C"); + var members = cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + AssertEx.Equal([ + "System.Int32 C.P { get; }", + "System.Int32 C.P.get", + "System.Int32 C._p", + "C..ctor()" + ], members); + + var propDefinition = comp.GetMember("C.P"); + Assert.True(propDefinition.IsPartialDefinition); + + var propImplementation = propDefinition.PartialImplementationPart!; + Assert.True(propImplementation.IsPartialImplementation); + + Assert.Same(propDefinition, propImplementation.PartialDefinitionPart); + Assert.Null(propImplementation.PartialImplementationPart); + Assert.Same(propImplementation, propDefinition.PartialImplementationPart); + Assert.Null(propDefinition.PartialDefinitionPart); + + Assert.Null(propDefinition.SetMethod); + Assert.Null(propImplementation.SetMethod); + + var definitionAccessor = propDefinition.GetMethod!; + var implementationAccessor = propImplementation.GetMethod!; + Assert.True(definitionAccessor.IsPartialDefinition()); + Assert.True(implementationAccessor.IsPartialImplementation()); + + Assert.Same(implementationAccessor, definitionAccessor.PartialImplementationPart); + Assert.Null(definitionAccessor.PartialDefinitionPart); + Assert.Same(definitionAccessor, implementationAccessor.PartialDefinitionPart); + Assert.Null(implementationAccessor.PartialImplementationPart); + } + + [Theory] + [InlineData("set")] + [InlineData("init")] + public void Semantics_03(string accessorKind) + { + // set/init-only + var source = $$""" + using System; + + var c = new C() { P = 1 }; + + partial class C + { + public partial int P { {{accessorKind}}; } + } + + partial class C + { + public partial int P + { + {{accessorKind}} + { + Console.Write(value); + } + } + } + """; + var verifier = CompileAndVerify([source, IsExternalInitTypeDefinition], expectedOutput: "1"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL($"C.P.{accessorKind}", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "void System.Console.Write(int)" + IL_0006: ret + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var cClass = comp.GetMember("C"); + var members = cClass.GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + + if (accessorKind == "set") + { + AssertEx.Equal([ + "System.Int32 C.P { set; }", + "void C.P.set", + "C..ctor()" + ], members); + } + else + { + AssertEx.Equal([ + "System.Int32 C.P { init; }", + "void modreq(System.Runtime.CompilerServices.IsExternalInit) C.P.init", + "C..ctor()" + ], + members); + } + + var propDefinition = comp.GetMember("C.P"); + Assert.True(propDefinition.IsPartialDefinition); + + var propImplementation = propDefinition.PartialImplementationPart!; + Assert.True(propImplementation.IsPartialImplementation); + + Assert.Same(propDefinition, propImplementation.PartialDefinitionPart); + Assert.Null(propImplementation.PartialImplementationPart); + Assert.Same(propImplementation, propDefinition.PartialImplementationPart); + Assert.Null(propDefinition.PartialDefinitionPart); + + Assert.Null(propDefinition.GetMethod); + Assert.Null(propImplementation.GetMethod); + + var definitionAccessor = propDefinition.SetMethod!; + var implementationAccessor = propImplementation.SetMethod!; + Assert.True(definitionAccessor.IsPartialDefinition()); + Assert.True(implementationAccessor.IsPartialImplementation()); + + Assert.Same(implementationAccessor, definitionAccessor.PartialImplementationPart); + Assert.Null(definitionAccessor.PartialDefinitionPart); + Assert.Same(definitionAccessor, implementationAccessor.PartialDefinitionPart); + Assert.Null(implementationAccessor.PartialImplementationPart); + } + + [Theory] + [InlineData("public partial int P { get => _p; set => _p = value; }")] + [InlineData("public partial int P { set => _p = value; get => _p; }")] + public void Semantics_04(string implementationPart) + { + // ordering difference between def and impl + var source = $$""" + using System; + + var c = new C() { P = 1 }; + Console.Write(c.P); + + partial class C + { + public partial int P { get; set; } + + private int _p; + {{implementationPart}} + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; + + var members = comp.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "System.Int32 C.P.get", + "void C.P.set", + "System.Int32 C._p", + "C..ctor()" + ], members); + + var reference = comp.EmitToImageReference(); + var comp1 = CreateCompilation([], options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All), references: [reference]); + var members1 = comp1.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + AssertEx.Equal([ + "System.Int32 C._p", + "System.Int32 C.P.get", + "void C.P.set", + "C..ctor()", + "System.Int32 C.P { get; set; }" + ], members1); + } + + [Theory] + [InlineData("public partial int P { get => _p; set => _p = value; }")] + [InlineData("public partial int P { set => _p = value; get => _p; }")] + public void Semantics_05(string implementationPart) + { + // ordering difference between def and impl (def set before get) + var source = $$""" + using System; + + var c = new C() { P = 1 }; + Console.Write(c.P); + + partial class C + { + public partial int P { set; get; } + + private int _p; + {{implementationPart}} + } + """; + + var verifier = CompileAndVerify(source, expectedOutput: "1"); + verifier.VerifyDiagnostics(); + var comp = (CSharpCompilation)verifier.Compilation; + + var members = comp.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + // set accessor appears before get accessor, otherwise member order and symbol display is the same as Semantics_04 + AssertEx.Equal([ + "System.Int32 C.P { get; set; }", + "void C.P.set", + "System.Int32 C.P.get", + "System.Int32 C._p", + "C..ctor()" + ], members); + + var reference = comp.EmitToImageReference(); + var comp1 = CreateCompilation([], options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All), references: [reference]); + var members1 = comp1.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString()); + AssertEx.Equal([ + "System.Int32 C._p", + "void C.P.set", + "System.Int32 C.P.get", + "C..ctor()", + "System.Int32 C.P { get; set; }" + ], members1); } [Fact] @@ -482,7 +1000,8 @@ partial class C comp.VerifyEmitDiagnostics(); } - // PROTOTYPE(partial-properties): test more mismatching scenarios + // PROTOTYPE(partial-properties): override partial property where base has modopt + // PROTOTYPE(partial-properties): unsafe context differences between partial property declarations // PROTOTYPE(partial-properties): test indexers incl parameters with attributes // PROTOTYPE(partial-properties): test merging property attributes }