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

Readonly members syntax and symbol API #32888

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Jan 28, 2019

Modified syntax checks and added symbol API to support the readonly members feature. See dotnet/csharplang#1710.

I outlined how I think the entire development of this feature breaks down in #32911. Would also appreciate feedback on that.

@RikkiGibson RikkiGibson added this to the 16.1.P1 milestone Jan 28, 2019
@RikkiGibson RikkiGibson requested a review from a team as a code owner January 28, 2019 22:19
@tannergooding
Copy link
Member

Any other kinds of declarations I need to cover?

While somewhat nonsensical from an API design point of view, I would think that any method (including property setters) should be applicable.

That is, it is entirely possible for a setter to not mutate any instance state (perhaps, for example, it is an instance property that only modifies some static state), which would make it valid as a readonly member. You can already do this in existing readonly structs, for example.

So, I believe the end-result should be that:

  • A readonly struct implicitly marks all members as readonly and requires that all fields be explicitly readonly
  • A regular struct can therefore have any of its members explicitly marked as readonly (but does not require fields to be readonly)

Additionally, given that a readonly struct requires that its fields be marked as readonly (thereby explicitly disallowing assignment), it would be beneficial it a readonly method would warn if you try to assign a non-readonly field. I realize we can't do this for method calls, due to back-compat reasons with the existing readonly struct feature; but I think field assignment for readonly methods would be fair game (since it is a new warning that only shows in new code).

@RikkiGibson
Copy link
Contributor Author

I think you're right @tannergooding. It's probably not uncommon for a struct setter to modify an object contained in a struct field, without violating the readonly constraint on the struct itself.

@jaredpar
Copy link
Member

@tannergooding

Additionally, given that a readonly struct requires that its fields be marked as readonly (thereby explicitly disallowing assignment), it would be beneficial it a readonly method would warn if you try to assign a non-readonly field.

This is explicitly an error. The type of this in a readonly member is in this hence field assignment is not possible.

That being said any invocation on this or a field of this of a member that isn't readonly should be flagged with a warning. Such an invocation will involve a defensive copy which is probably un-intended.

That being said (part 2) we didn't do this for readonly struct which in reprotrospect we probably should have. Should take this up with LDM to see where they want to land on this.

@jaredpar
Copy link
Member

@RikkiGibson

My understanding is that the possible "readonly members" include get accessors and methods on structs. Any other kinds of declarations I need to cover?

All members types: operators, constructors, etc ... 😄

Even though readonly is only legal on a subset of members we need validate that it's illegal on the others. Hence testing should include non-sense cases like a readonly constructor on a struct. Note: for the property case you also need to validate the expression bodied and traditional syntaxes.

@tannergooding
Copy link
Member

This is explicitly an error. The type of this in a readonly member is in this hence field assignment is not possible.

Great. This is even better.

That being said any invocation on this or a field of this of a member that isn't readonly should be flagged with a warning. Such an invocation will involve a defensive copy which is probably un-intended.

That would be great, but I understand the limitations around "breaking" changes for the existing readonly struct. Perhaps, as a compromise (while we don't have warning waves), it could be enabled for methods explicitly marked readonly. That would be non-breaking (and easy to use for the new feature) and would allow users of the existing readonly struct feature to have a "backdoor" (although a verbose one) to get those warnings if desired.

@RikkiGibson RikkiGibson force-pushed the features/readonly-members branch from 8721918 to e0fd8a2 Compare February 20, 2019 01:33
@RikkiGibson RikkiGibson changed the title Basic syntax tests for readonly members Readonly members syntax and symbol API Feb 25, 2019
@CyrusNajmabadi
Copy link
Member

Not looking deeply into this, but this commit message seems super strange:
Add ReadOnlyKeyword to ArrowExpressionClauseSyntax.

Why would a ReadOnlyKeyword be attached to the ArrowExpression? That seems like a super bizarre place to put it.

@RikkiGibson RikkiGibson force-pushed the features/readonly-members branch from 3af5d21 to c3daaae Compare February 26, 2019 21:24
@RikkiGibson
Copy link
Contributor Author

@dotnet/roslyn-compiler This PR is ready for review.

@RikkiGibson
Copy link
Contributor Author

FYI @sharwell the release mode integration tests failed. Retrying.

@cston
Copy link
Member

cston commented Feb 27, 2019

        }

Do we need to check IsStatic && IsReadOnly here?


Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs:938 in 7da2c02. [](commit_id = 7da2c02, deletion_comment = False)

@@ -410,7 +410,12 @@ private DeclarationModifiers MakeModifiers(AccessorDeclarationSyntax syntax, Loc
const DeclarationModifiers defaultAccess = DeclarationModifiers.None;

// Check that the set of modifiers is allowed
const DeclarationModifiers allowedModifiers = DeclarationModifiers.AccessibilityMask;
DeclarationModifiers allowedModifiers = DeclarationModifiers.AccessibilityMask;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to check that the parent property this accessor belongs to isn't readonly?

For access modifiers it's an error when you specify an accessor modifier that isn't more restrictive https://sharplab.io/#v2:D4AQDABCCMDcCwAoEBmKAmCBhCBvJEhBhUaMkAysYfoiYQJADmApgC4J30QOoQDO7TvQC+SEUA==

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like that would be consistent with the behavior for visibility modifiers.

Do you propose giving a similar error for this case?

struct S
{
    readonly string P
    {
     	readonly get;
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that's what I was thinking. Should we be giving an error in that case? I don't really have an opinion one way or the other, but it seems like it would be consistent if we did.


In reply to: 260932737 [](ancestors = 260932737)

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 went ahead and made it an error to provide readonly on an accessor for a property marked readonly.

@RikkiGibson
Copy link
Contributor Author

        }

I believe no because the accessors will "inherit" the static readonly modifiers from the property, and we will catch it there. It might give better diagnostics in some cases, however-- e.g. static readonly int P { get; set; } may produce 2 very similar diagnostics. I'll ensure there's a test for that.


In reply to: 468014549 [](ancestors = 468014549)


Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs:938 in 7da2c02. [](commit_id = 7da2c02, deletion_comment = False)

@@ -451,6 +456,11 @@ private void CheckModifiers(Location location, bool hasBody, bool isAutoProperty
{
diagnostics.Add(AccessCheck.GetProtectedMemberInSealedTypeError(ContainingType), location, this);
}
else if (IsStatic && IsReadOnly && !_property.HasReadOnlyModifier)
Copy link
Member

Choose a reason for hiding this comment

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

&& !_property.HasReadOnlyModifier [](start = 44, length = 33)

Is this check needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this scenario: static readonly int P { get; set; } this check allows us to give a diagnostic only on the property and not on the accessors, which inherit their modifiers from the property. (is there a better term to use than "inherit"?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a better pattern to use to prevent redundant diagnostics?

@@ -627,6 +627,8 @@ internal bool IsNew
get { return (_modifiers & DeclarationModifiers.New) != 0; }
}

internal bool HasReadOnlyModifier => (_modifiers & DeclarationModifiers.ReadOnly) != 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This might have to become public API at some point, so we'll have to look at naming. IsReadOnly means something else on properties.

@RikkiGibson
Copy link
Contributor Author

@dotnet/roslyn-compiler could I get a second review please?

@CyrusNajmabadi
Copy link
Member

@RikkiGibson is it also on you to do the IDE side of things? If so, are you intending to do that through another set of PRs?

@RikkiGibson
Copy link
Contributor Author

Yeah, that will happen in separate PRs.

// (9,9): error CS1609: Modifiers cannot be placed on event accessor declarations
// readonly remove {}
Diagnostic(ErrorCode.ERR_NoModifiersOnAccessor, "readonly").WithLocation(9, 9));
}
Copy link
Member

Choose a reason for hiding this comment

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

Should add a test or PROTOTYPE about ensuring this only works when C# 8.0 or greater is enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I will probably have a PR specifically about ensuring feature checks occur appropriately in all scenarios (basically wherever the readonly modifier can be added).

@RikkiGibson RikkiGibson merged commit 6752e31 into dotnet:features/readonly-members Mar 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants