Enforce unconditional attributes within method bodies#40789
Enforce unconditional attributes within method bodies#40789jcouv merged 13 commits intodotnet:masterfrom
Conversation
|
|
||
| if (_expressionIsRead) | ||
| { | ||
| ReportMaybeNullFromTypeParameterValueIfNeeded(node, resultType, memberAnnotations); |
There was a problem hiding this comment.
📝 removed empty method #Resolved
|
@dotnet/roslyn-compiler @cston for review. Thanks I'll add a note to breaking changes. #Closed |
| /// Note this does not work for nullable value types, so an additional check with <see cref="CheckDisallowedNullAssignment"/> may be required. | ||
| /// </summary> | ||
| private static TypeWithAnnotations ApplyLValueAnnotations(TypeWithAnnotations declaredType, FlowAnalysisAnnotations flowAnalysisAnnotations) | ||
| internal static TypeWithAnnotations ApplyLValueAnnotations(TypeWithAnnotations declaredType, FlowAnalysisAnnotations flowAnalysisAnnotations) |
There was a problem hiding this comment.
internal [](start = 8, length = 8)
Is this change needed? #Closed
| // [NotNull]TNullable -> X | ||
| // [NotNull]TStruct? -> X | ||
| // [NotNull]TOpen -> X | ||
| return type is null || (type.Kind == SymbolKind.TypeParameter && !type.IsReferenceType) || type.IsNullableTypeOrTypeParameter(); |
There was a problem hiding this comment.
type is null [](start = 23, length = 12)
When do we hit this, and is it necessary to report a warning in that case? #Resolved
| } | ||
| } | ||
|
|
||
| FlowAnalysisAnnotations ToInwardAnnotations(FlowAnalysisAnnotations outwardAnnotations) |
| </data> | ||
| <data name="WRN_DisallowNullAttributeForbidsMaybeNullAssignment" xml:space="preserve"> | ||
| <value>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</value> | ||
| <value>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute or output to a target marked with the [NotNull] attribute</value> |
There was a problem hiding this comment.
output to a target marked with [](start = 109, length = 30)
Is this qualifier necessary? Do we need to differentiate "assigned to" and "output to", or can we find one phrase that captures both?
How about: "A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]"? #Closed
| [DisallowNull] string? P1 { get; set; } = """"; | ||
| [AllowNull] string P2 { get; set; } = """"; | ||
| [MaybeNull] string P3 { get; set; } = """"; | ||
| [NotNull] string? P4 { get; set; } = """"; |
There was a problem hiding this comment.
It's not clear we're that the attributes affect the property bodies since there are no warnings. Should the assignments be changed to = null' to at least test two of the four? #Closed
There was a problem hiding this comment.
💡 The previous test has good coverage of these, so a starting point could model after it. #Resolved
|
|
||
| [Fact] | ||
| [WorkItem(36039, "https://github.com/dotnet/roslyn/issues/36039")] | ||
| public void NontNullOutParameterWithVariousTypes() |
There was a problem hiding this comment.
Nont [](start = 20, length = 4)
Typo #Closed
| x.ToString(); // 1 | ||
| } | ||
|
|
||
| void M2([DisallowNull]T x) |
There was a problem hiding this comment.
📝 Should have a test showing that x = default is allowed within a method like this. #Closed
There was a problem hiding this comment.
@sharwell I disagree with this one. x = default is disallowed (ie. a warning) in absence of [DisallowNull], so should still be disallowed. #Closed
There was a problem hiding this comment.
That makes sense to me. Consider adding a line showing this diagnostic is reported for the case. #Resolved
There was a problem hiding this comment.
I think that case is already covered. See DefaultLiteralInConditional #Resolved
| [DisallowNull] string? P1 { get; set; } = """"; | ||
| [AllowNull] string P2 { get; set; } = """"; | ||
| [MaybeNull] string P3 { get; set; } = """"; | ||
| [NotNull] string? P4 { get; set; } = """"; |
There was a problem hiding this comment.
💡 The previous test has good coverage of these, so a starting point could model after it. #Resolved
| // (4,43): warning CS8607: A possible null value may not be assigned to a target marked with the [DisallowNull] attribute or output to a target marked with the [NotNull] attribute | ||
| // [AllowNull, NotNull]public TOpen P1 { get; set; } = default!; | ||
| Diagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, "get;").WithLocation(4, 43), |
There was a problem hiding this comment.
😟 This makes sense if you have { get; }, but I don't see why this would be a warning if the property has a setter and has [AllowNull], especially since it uses the suppression in default!. #Closed
There was a problem hiding this comment.
We analyze a body that says return backingField;. But that backing field is a TOpen and lacks attribute annotations. So we warn.
I'll take a look to see if we can refine that. #Closed
There was a problem hiding this comment.
I find out that some warnings involving backing fields and other compiler-generated values were suppressed. I removed that, which not only keeps this warning on P1, but also adds a warning on P3.
@sharwell @cston Let me know what you think about those. In my opinion, those are bad usages of the attributes, and so I think it's actually desirable to warn. #Resolved
There was a problem hiding this comment.
Both cases can be resolved (in user code) by removing the unnecessary initializing expression. If users find this confusing, we can iterate on it. #Resolved
There was a problem hiding this comment.
From discussion with @cston I'm going to restore the logic to skip analyzing bodies of auto-prop getters and setters. We'll lose some warning about misused attributes, but that's okay.
One reason is that there is a scenario where that would produce an unwanted warning: [MaybeNull, AllowNull] T { get; set; } which the user can't fix. #Resolved
| Diagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, "get;").WithLocation(4, 32), | ||
| // (14,35): warning CS8607: A possible null value may not be assigned to a target marked with the [DisallowNull] attribute or output to a target marked with the [NotNull] attribute | ||
| // [NotNull]public TStruct? P5 { get; set; } | ||
| Diagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, "get;").WithLocation(14, 35) |
There was a problem hiding this comment.
💡 It would be good to have a test showing that the value can be assigned in a constructor to eliminate the warning (may already exist as a test). #Resolved
There was a problem hiding this comment.
❔ I didn't see a test that used the following in a constructor:
P5 = new TStruct();
Unlike the newly-added NotNull_Property_InitializedInConstructor cases, I would expect the above to not produce a warning and resolve the warning reported on the property definition in the two tests here. #Pending
|
|
||
| static bool hasNoNonNullableCounterpart(TypeSymbol type) | ||
| { | ||
| if (type is null) |
There was a problem hiding this comment.
📝 not sure if we can hit this. Just included for safety. #Resolved
| if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && state.Type.IsNullableTypeOrTypeParameter() && state.MayBeNull) | ||
| if (boundValueOpt is object && boundValueOpt.WasCompilerGenerated) | ||
| { | ||
| // We need to skip `return backingField;` in auto-prop getters |
There was a problem hiding this comment.
// We need to skip
return backingField;in auto-prop getters [](start = 16, length = 62)
Can we simply avoid running NullableWalker on auto-property accessors? #Resolved
There was a problem hiding this comment.
We could, but I'd prefer to contain the scope of this PR given timeline.
Also, even if we skip auto-prop accessors, we'll still need some logic to skip compiler-generated nodes because of field = initializerValue; that is injected to constructor body and analyzed.
In reply to: 367228167 [](ancestors = 367228167)
There was a problem hiding this comment.
Please consider logging a bug to skip auto-property accessors.
In reply to: 367632535 [](ancestors = 367632535,367228167)
| private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location, BoundExpression boundValueOpt = null) | ||
| { | ||
| if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && state.Type.IsNullableTypeOrTypeParameter() && state.MayBeNull) | ||
| if (boundValueOpt is object && boundValueOpt.WasCompilerGenerated) |
There was a problem hiding this comment.
boundValueOpt is object && boundValueOpt.WasCompilerGenerated [](start = 16, length = 61)
Consider using ?. #Resolved
|
@dotnet/roslyn-compiler for a second review please. |
| } | ||
|
|
||
| private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location) | ||
| private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location, BoundExpression boundValueOpt = null) |
There was a problem hiding this comment.
Consider naming CheckDisallowNullAssignment to more strongly denote that this is about DisallowNullAttribute #Resolved
There was a problem hiding this comment.
I think that "check disallowed null assignment" rings better as an action. I'll keep as-is
In reply to: 368161189 [](ancestors = 368161189)
| } | ||
| } | ||
|
|
||
| private static FlowAnalysisAnnotations ToInwardAnnotations(FlowAnalysisAnnotations outwardAnnotations) |
There was a problem hiding this comment.
ToInwardAnnotations [](start = 47, length = 19)
Consider naming ToInboundAnnotations to resemble similar terminology used in VisitArguments
| // MaybeNull and MaybeNullWhen count as MaybeNull | ||
| annotations |= FlowAnalysisAnnotations.AllowNull; | ||
| } | ||
| if ((outwardAnnotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull) |
There was a problem hiding this comment.
Style between this and the above if feels inconsistent #Closed
There was a problem hiding this comment.
Yes, that's on purpose. MaybeNull is two bits, either of which should count (see comment above).
NotNull is also two bits, but we are lenient (we ignore NotNullWhenTrue and NotNullWhenFalse on their own here).
In reply to: 368161275 [](ancestors = 368161275)
There was a problem hiding this comment.
| // static void F4<T>( [AllowNull]T x, [MaybeNull]ref T y) { y = x; } | ||
| Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "x").WithLocation(8, 71), | ||
| // (9,71): warning CS8601: Possible null reference assignment. | ||
| // static void F5<T>( [AllowNull]T x, [NotNull]ref T y) { y = x; } // 4 |
There was a problem hiding this comment.
T x [](start = 55, length = 3)
I think this is maybe innate to the problem, but it feels odd to have a different message here depending on whether AllowNull was used. In F5, T is "definitely maybe null", but in FA, T is "possibly maybe null". #WontFix
There was a problem hiding this comment.
We could have the new check (from CheckDisallowedNullAssignment) report the same diagnostic as the regular checks that VisitConversion can report. But since we have more context/information here, I figured out that some hint would be helpful, when possible. But it is a bit inconsistent, I agree.
In reply to: 368176797 [](ancestors = 368176797)
It feels like Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:119870 in 501855b. [](commit_id = 501855b, deletion_comment = True) |
It feels like if assigning maybe-null to a Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:119847 in 501855b. [](commit_id = 501855b, deletion_comment = True) |
| @"using System.Diagnostics.CodeAnalysis; | ||
| public class COpen<TOpen> where TOpen : new() | ||
| { | ||
| [NotNull]public TOpen P1 { get; set; } |
There was a problem hiding this comment.
Is this just additional coverage, or did your change affect behavior of these scenarios? It doesn't seem like enforcing unconditional attributes inside the members using the attributes affects this? #Resolved
There was a problem hiding this comment.
For a while, this PR include analyzing the body of the auto-prop accessors. So those tests were useful.
Since I added the tests, I'd rather keep them even though they are less useful now :-)
In reply to: 368177028 [](ancestors = 368177028)
| class Program | ||
| { | ||
| [return: MaybeNull, NotNull] static T F1<T>(T t) => t; | ||
| [return: MaybeNull, NotNull] static T F1<T>(T t) => t; // |
There was a problem hiding this comment.
// [](start = 59, length = 2)
Was typing // -1 a bridge too far? :) #Resolved
There was a problem hiding this comment.
| }"; | ||
| var comp = CreateCompilation(new[] { DisallowNullAttributeDefinition, source }, options: WithNonNullTypesTrue()); | ||
| comp.VerifyDiagnostics( | ||
| // (11,12): warning CS8607: A possible null value may not be assigned to a target marked with the [DisallowNull] attribute |
There was a problem hiding this comment.
nit: Consider updating the diagnosic message in this comment. I had started to comment about how this message could be better but it turned out you already changed it for the better :)
Per our discussion and spec, In reply to: 575833532 [](ancestors = 575833532) Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:119870 in 501855b. [](commit_id = 501855b, deletion_comment = True) |
|
FYI @cston, I took Rikki's suggestion so that |
Never mind, that won't work because of |
Addresses part of #36039
MaybeNullWhenandNotNullWhenare treated unconditionally to produce fewer warnings.DoesNotReturnandDoesNotReturnIfare not yet enforced.OHI will come later.
Fixes #40139 (
[DisallowNull] Tparameter has not-null initial value)Fixes #39922
Relates to discussion in LDM on Jan 6th 2020.