Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial properties: misc compiler layer work #73527

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,6 @@ 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,14 @@ public override void DefaultVisit(Symbol symbol)
bool shouldSkipPartialDefinitionComments = false;
if (symbol.IsPartialDefinition())
{
if (symbol is MethodSymbol { PartialImplementationPart: MethodSymbol implementationPart })
Symbol? implementationPart = symbol switch
{
MethodSymbol method => method.PartialImplementationPart,
SourcePropertySymbol property => property.PartialImplementationPart,
_ => null
};

if (implementationPart is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this existing check is actually correct for partial symbols that can't skip implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be concerned about a scenario like this: (SharpLab for analogous method scenario)

public partial class C {
    void M0()
    {
        _ = Prop; // on hover do we see the doc comment?
    }
    
    /// <summary>Property comment</summary>
    partial int Prop { get; } // no impl
}

Thankfully I think the !_isForSingleSymbol logic on line 288 (at time of writing this comment) ensures that Quick Info does show a doc comment here. We could add a test.

{
Visit(implementationPart);

Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ internal enum MessageID
IDS_FeatureParamsCollections = MessageBase + 12842,

IDS_FeatureRefUnsafeInIteratorAsync = MessageBase + 12843,

// PROTOTYPE(partial-properties): pack
IDS_FeaturePartialProperties = MessageBase + 13000,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -469,6 +472,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureLockObject:
case MessageID.IDS_FeatureParamsCollections:
case MessageID.IDS_FeatureRefUnsafeInIteratorAsync:
case MessageID.IDS_FeaturePartialProperties:
return LanguageVersion.Preview;

// C# 12.0 features.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,18 @@ private static (DeclarationModifiers modifiers, bool hasExplicitAccessMod) MakeM
hasExplicitAccessMod = true;
}

if ((mods & DeclarationModifiers.Partial) != 0)
{
Debug.Assert(location.SourceTree is not null);

LanguageVersion availableVersion = ((CSharpParseOptions)location.SourceTree.Options).LanguageVersion;
LanguageVersion requiredVersion = MessageID.IDS_FeaturePartialProperties.RequiredVersion();
if (availableVersion < requiredVersion)
{
ModifierUtils.ReportUnsupportedModifiersForLanguageVersion(mods, DeclarationModifiers.Partial, location, diagnostics, availableVersion, requiredVersion);
}
}

ModifierUtils.CheckFeatureAvailabilityForStaticAbstractMembersInInterfacesIfNeeded(mods, isExplicitInterfaceImplementation, location, diagnostics);

containingType.CheckUnsafeModifier(mods, location, diagnostics);
Expand Down Expand Up @@ -631,7 +643,6 @@ private void PartialPropertyChecks(SourcePropertySymbol implementation, BindingD
}

if ((!hasTypeDifferences && !MemberSignatureComparer.PartialMethodsStrictComparer.Equals(this, implementation))
// PROTOTYPE(partial-properties): test indexers with parameter name differences
|| !Parameters.SequenceEqual(implementation.Parameters, (a, b) => a.Name == b.Name))
{
diagnostics.Add(ErrorCode.WRN_PartialPropertySignatureDifference, implementation.GetFirstLocation(),
Expand Down Expand Up @@ -705,11 +716,13 @@ private void PartialPropertyChecks(SourcePropertySymbol implementation, BindingD
private static BaseParameterListSyntax? GetParameterListSyntax(CSharpSyntaxNode syntax)
=> (syntax as IndexerDeclarationSyntax)?.ParameterList;

public sealed override bool IsExtern => PartialImplementationPart is { } implementation ? implementation.IsExtern : HasExternModifier;

internal SourcePropertySymbol? OtherPartOfPartial => _otherPartOfPartial;

internal bool IsPartialDefinition => IsPartial && !AccessorsHaveImplementation && !IsExtern;
internal bool IsPartialDefinition => IsPartial && !AccessorsHaveImplementation && !HasExternModifier;

internal bool IsPartialImplementation => IsPartial && (AccessorsHaveImplementation || IsExtern);
internal bool IsPartialImplementation => IsPartial && (AccessorsHaveImplementation || HasExternModifier);

internal SourcePropertySymbol? PartialDefinitionPart => IsPartialImplementation ? OtherPartOfPartial : null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,20 @@ public override bool IsAbstract
get { return (_modifiers & DeclarationModifiers.Abstract) != 0; }
}

protected bool HasExternModifier
{
get
{
return (_modifiers & DeclarationModifiers.Extern) != 0;
}
}

public override bool IsExtern
{
get { return (_modifiers & DeclarationModifiers.Extern) != 0; }
get
{
return HasExternModifier;
}
}

public override bool IsStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,6 @@ 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()
{
Expand Down Expand Up @@ -240,6 +238,69 @@ partial void PartialMethod() { }
Assert.True(completedCompilationUnits.Contains(tree1.FilePath));
}

[Fact]
public void TestCompilationEventsForPartialProperty()
{
var source1 = @"
namespace N1
{
partial class Class
{
int NonPartialProp1 { get; set; }
partial int DefOnlyPartialProp { get; set; }
partial int ImplOnlyPartialProp { get => 1; set { } }
partial int PartialProp { get; set; }
}
}
";
var source2 = @"
namespace N1
{
partial class Class
{
int NonPartialProp2 { get; set; }
partial int PartialProp { get => 1; set { } }
}
}
";

var tree1 = CSharpSyntaxTree.ParseText(source1, path: "file1");
var tree2 = CSharpSyntaxTree.ParseText(source2, path: "file2");
var eventQueue = new AsyncQueue<CompilationEvent>();
var compilation = CreateCompilationWithMscorlib45(new[] { tree1, tree2 }).WithEventQueue(eventQueue);

// Invoke SemanticModel.GetDiagnostics to force populate the event queue for symbols in the first source file.
var model = compilation.GetSemanticModel(tree1);
model.GetDiagnostics(tree1.GetRoot().FullSpan);

Assert.True(eventQueue.Count > 0);
bool compilationStartedFired;
HashSet<string> declaredSymbolNames, completedCompilationUnits;
Assert.True(DequeueCompilationEvents(eventQueue, out compilationStartedFired, out declaredSymbolNames, out completedCompilationUnits));

// Verify symbol declared events fired for all symbols declared in the first source file.
Assert.True(compilationStartedFired);

// NB: NonPartialProp2 is missing here because we only asked for diagnostics in tree1
AssertEx.Equal([
"",
"Class",
"DefOnlyPartialProp",
"get_ImplOnlyPartialProp",
"get_NonPartialProp1",
"get_PartialProp",
"ImplOnlyPartialProp",
"N1",
"NonPartialProp1",
"PartialProp",
"set_ImplOnlyPartialProp",
"set_NonPartialProp1",
"set_PartialProp"
], declaredSymbolNames.OrderBy(name => name));

AssertEx.Equal(["file1"], completedCompilationUnits.OrderBy(name => name));
}

[Fact, WorkItem(8178, "https://github.com/dotnet/roslyn/issues/8178")]
public void TestEarlyCancellation()
{
Expand Down Expand Up @@ -288,12 +349,7 @@ private static bool DequeueCompilationEvents(AsyncQueue<CompilationEvent> eventQ
var added = declaredSymbolNames.Add(symbol.Name);
if (!added)
{
var method = symbol.GetSymbol() as Symbols.MethodSymbol;
Assert.NotNull(method);

var isPartialMethod = method.PartialDefinitionPart != null ||
method.PartialImplementationPart != null;
Assert.True(isPartialMethod, "Unexpected multiple symbol declared events for symbol " + symbol);
Assert.True(symbol.GetSymbol().IsPartialMember(), "Unexpected multiple symbol declared events for symbol " + symbol);
}
}
else
Expand Down
Loading
Loading