-
Notifications
You must be signed in to change notification settings - Fork 20
LFT-2910: Detect mutability from primary class constructors #977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,10 @@ public bool CheckDeclaration( INamedTypeSymbol type ) { | |
| .Where( m => !m.IsStatic ); | ||
|
|
||
| if( type.TypeKind == TypeKind.Class ) { | ||
| if( HasPrimaryClassConstructorMutability( type ) ) { | ||
| result = false; | ||
| } | ||
|
|
||
| // Check that the base class is immutable for classes | ||
| var baseClassOk = m_context.IsImmutable( | ||
| new ImmutabilityQuery( | ||
|
|
@@ -81,6 +85,35 @@ out var diag | |
| return result; | ||
| } | ||
|
|
||
| public bool HasPrimaryClassConstructorMutability( INamedTypeSymbol @class ) { | ||
| foreach( var constructor in @class.InstanceConstructors ) { | ||
| foreach( var syntaxRef in constructor.DeclaringSyntaxReferences ) { | ||
| var syntax = syntaxRef.GetSyntax( m_cancellationToken ); | ||
|
|
||
| // Primary constructors for classes introduce mutability. | ||
| if( syntax is not ClassDeclarationSyntax ctor ) { | ||
| continue; | ||
| } | ||
|
|
||
| // Unimportant edge case | ||
| if( ctor.ParameterList.Parameters.Count == 0 ) { | ||
| return false; | ||
| } | ||
|
|
||
| m_diagnosticSink( | ||
| Diagnostic.Create( | ||
| Diagnostics.PrimaryClassConstructorIntroducesMutability, | ||
| ctor.ParameterList.GetLocation() | ||
| ) | ||
| ); | ||
|
Comment on lines
+103
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, this is only the case if the parameters get used outside of field initializers. Really we just want the Roslyn team's RS0062, or to add an equivalent. https://andrewlock.net/blocking-primary-constructor-member-capture-using-an-analyzer/ using System;
sealed class A( string P ) {
void M() {
Console.WriteLine( P );
}
}
sealed class B( string P ) {
private readonly string m_p = P;
void M() {
Console.WriteLine( m_p );
}
}using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue | DebuggableAttribute.DebuggingModes.DisableOptimizations)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[NullableContext(1)]
[Nullable(0)]
internal sealed class A
{
public A(string P)
{
<P>P = P;
base..ctor();
}
private void M()
{
Console.WriteLine(<P>P);
}
}
[NullableContext(1)]
[Nullable(0)]
internal sealed class B
{
private readonly string m_p;
public B(string P)
{
m_p = P;
base..ctor();
}
private void M()
{
Console.WriteLine(m_p);
}
}
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to do this as a first pass though, 👍
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I agree with
In that I think this analyzer should stand on it's own (if that counts as "adding an equivalent" then OK). As written this will prevent okay usages of primary class constructors for immutable types, so I do agree that this is a "first pass" in that sense (I wasn't planning to do any more of it, though -- this was motivated by the thread in Slack and existing (probably benign) usages that have snuck through).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is probably 99% agreement, I'll chat with you on Slack about next steps |
||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /// <remarks> | ||
| /// Check that a member (e.g. field or property) always produces immutable | ||
| /// values. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some open chatter in the compiler about exposing more of this stuff via the semantic model but the current situation is you need to go into the syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might wonder like don't they show up as properties or something in the GetMembers() call earlier, and the answer is that no they don't.