-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add switch to skip nullable analysis #49876
Changes from 1 commit
27ccf02
92c6bee
20bad7b
dbddbcd
74b390f
9765a57
3c759ca
8e4fb81
3756c6a
4a324a5
88e8a69
259663d
b38ef12
0c22385
35ad85d
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 |
---|---|---|
|
@@ -216,25 +216,33 @@ internal bool ShouldRunNullableWalker | |
{ | ||
if (!_lazyShouldRunNullableWalker.HasValue()) | ||
{ | ||
_lazyShouldRunNullableWalker = calculateResult(); | ||
} | ||
return _lazyShouldRunNullableWalker.Value(); | ||
|
||
ThreeState calculateResult() | ||
{ | ||
var feature = Feature("nullableAnalysis"); | ||
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.
It is confusing to have this new "nullable-analysis" switch (used for all calls to I'll re-purpose "run-nullable-analysis" to cover all calls. And I'll change the expected values from
|
||
if (feature == "false") | ||
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. Consider having a central method that returns a |
||
{ | ||
return ThreeState.False; | ||
} | ||
|
||
if (Options.NullableContextOptions != NullableContextOptions.Disable) | ||
{ | ||
_lazyShouldRunNullableWalker = ThreeState.True; | ||
return true; | ||
return ThreeState.True; | ||
} | ||
|
||
foreach (var syntaxTree in SyntaxTrees) | ||
{ | ||
if (((CSharpSyntaxTree)syntaxTree).HasNullableEnables()) | ||
{ | ||
_lazyShouldRunNullableWalker = ThreeState.True; | ||
return true; | ||
return ThreeState.True; | ||
} | ||
} | ||
|
||
_lazyShouldRunNullableWalker = ThreeState.False; | ||
return ThreeState.False; | ||
} | ||
|
||
return _lazyShouldRunNullableWalker.Value(); | ||
} | ||
} | ||
|
||
|
@@ -399,7 +407,7 @@ private CSharpCompilation( | |
SyntaxAndDeclarationManager syntaxAndDeclarations, | ||
IReadOnlyDictionary<string, string> features, | ||
SemanticModelProvider? semanticModelProvider, | ||
AsyncQueue<CompilationEvent>? eventQueue = null) | ||
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.
It is not clear what is the motivation for this change. #Closed |
||
AsyncQueue<CompilationEvent>? eventQueue) | ||
: base(assemblyName, references, features, isSubmission, semanticModelProvider, eventQueue) | ||
{ | ||
WellKnownMemberSignatureComparer = new WellKnownMembersSignatureComparer(this); | ||
|
@@ -685,6 +693,9 @@ internal override Compilation WithEventQueue(AsyncQueue<CompilationEvent>? event | |
eventQueue); | ||
} | ||
|
||
// Nullable analysis data for methods, parameter default values, and attributes. Collected during testing only. | ||
internal ConcurrentDictionary<object, int>? NullableAnalysisData; | ||
|
||
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. Consider placing the field declaration at the beginning of the type declaration. I couldn't figure out why this place was chosen, it doesn't look like there is any relationship to surrounding code. #Closed |
||
#endregion | ||
|
||
#region Submission | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1075,12 +1075,15 @@ internal static void AnalyzeIfNeeded( | |
if (compilation.LanguageVersion < MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion() || !compilation.ShouldRunNullableWalker) | ||
{ | ||
#if DEBUG | ||
// Always run analysis in debug builds so that we can more reliably catch | ||
// nullable regressions e.g. https://github.com/dotnet/roslyn/issues/40136 | ||
// Once we address https://github.com/dotnet/roslyn/issues/46579 we should also always pass `getFinalNullableState: true` in debug mode. | ||
// We will likely always need to write a 'null' out for the out parameter in this code path, though, because | ||
// we don't want to introduce behavior differences between debug and release builds | ||
Analyze(compilation, method, node, new DiagnosticBag(), useConstructorExitWarnings: false, initialNullableState: null, getFinalNullableState: false, out _); | ||
if (ShouldRunAnalysisInDebug(compilation)) | ||
{ | ||
// Always run analysis in debug builds so that we can more reliably catch | ||
// nullable regressions e.g. https://github.com/dotnet/roslyn/issues/40136 | ||
// Once we address https://github.com/dotnet/roslyn/issues/46579 we should also always pass `getFinalNullableState: true` in debug mode. | ||
// We will likely always need to write a 'null' out for the out parameter in this code path, though, because | ||
// we don't want to introduce behavior differences between debug and release builds | ||
Analyze(compilation, method, node, new DiagnosticBag(), useConstructorExitWarnings: false, initialNullableState: null, getFinalNullableState: false, out _); | ||
} | ||
#endif | ||
finalNullableState = null; | ||
return; | ||
|
@@ -1089,6 +1092,13 @@ internal static void AnalyzeIfNeeded( | |
Analyze(compilation, method, node, diagnostics, useConstructorExitWarnings, initialNullableState, getFinalNullableState, out finalNullableState); | ||
} | ||
|
||
#if DEBUG | ||
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. Part of the motivation for the feature flag was to disable in production but seems like you're making it debug only here. #Resolved 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.
In reply to: 539661298 [](ancestors = 539661298) |
||
private static bool ShouldRunAnalysisInDebug(CSharpCompilation compilation) | ||
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. Think the method name is a bit off here to what it actually does. Think something closer to |
||
{ | ||
return compilation.Feature("nullableAnalysis") != "false"; | ||
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. Consider that in the future this switch is going to have to modes: force on and force off. Don't think |
||
} | ||
#endif | ||
|
||
internal static void Analyze( | ||
CSharpCompilation compilation, | ||
MethodSymbol method, | ||
|
@@ -1295,7 +1305,7 @@ internal static bool NeedsAnalysis(CSharpCompilation compilation) | |
#if DEBUG | ||
// Always run analysis in debug builds so that we can more reliably catch | ||
// nullable regressions e.g. https://github.com/dotnet/roslyn/issues/40136 | ||
return true; | ||
return ShouldRunAnalysisInDebug(compilation); | ||
#else | ||
var canSkipAnalysis = compilation.LanguageVersion < MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion() || !compilation.ShouldRunNullableWalker; | ||
return !canSkipAnalysis; | ||
|
@@ -1312,12 +1322,17 @@ internal static void AnalyzeIfNeeded( | |
if (compilation.LanguageVersion < MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion() || !compilation.ShouldRunNullableWalker) | ||
{ | ||
#if DEBUG | ||
// Always run analysis in debug builds so that we can more reliably catch | ||
// nullable regressions e.g. https://github.com/dotnet/roslyn/issues/40136 | ||
diagnostics = new DiagnosticBag(); | ||
#else | ||
return; | ||
if (ShouldRunAnalysisInDebug(compilation)) | ||
{ | ||
// Always run analysis in debug builds so that we can more reliably catch | ||
// nullable regressions e.g. https://github.com/dotnet/roslyn/issues/40136 | ||
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. This comment seems more applicable on the method now than the invocation of the method. #Resolved |
||
diagnostics = new DiagnosticBag(); | ||
} | ||
else | ||
#endif | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
Analyze( | ||
|
@@ -1436,26 +1451,16 @@ private static void Analyze( | |
{ | ||
ex.AddAnError(diagnostics); | ||
} | ||
} | ||
|
||
#if DEBUG | ||
#if REPORT_MAX | ||
private static (Symbol symbol, int slots) s_maxSlots; | ||
#endif | ||
#endif | ||
if (walker.compilation.NullableAnalysisData is { } state) | ||
{ | ||
var key = (object?)symbol ?? walker.methodMainNode.Syntax; | ||
state[key] = walker._variableSlot.Count; | ||
} | ||
} | ||
|
||
private SharedWalkerState SaveSharedState() | ||
{ | ||
#if DEBUG | ||
#if REPORT_MAX | ||
int slots = _variableSlot.Count; | ||
if (slots > s_maxSlots.slots) | ||
{ | ||
s_maxSlots = (_symbol, slots); | ||
Debug.WriteLine("{0}: {1}", slots, _symbol); | ||
} | ||
#endif | ||
#endif | ||
return new SharedWalkerState( | ||
_variableSlot.ToImmutableDictionary(), | ||
ImmutableArray.Create(variableBySlot, start: 0, length: nextVariableSlot), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,12 @@ | |
|
||
#nullable disable | ||
|
||
using System.Collections.Concurrent; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.CSharp.Test.Utilities; | ||
using Microsoft.CodeAnalysis.Test.Utilities; | ||
using Roslyn.Test.Utilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics | ||
|
@@ -149,5 +152,163 @@ void AssertEnabledForInheritence(NullableContext context, bool warningsEnabled, | |
Assert.True(contextInherited.AnnotationsInherited()); | ||
} | ||
} | ||
|
||
// See also CommandLineTests.NullableAnalysisFlags(). | ||
[Fact] | ||
public void NullableAnalysisFlags_01() | ||
{ | ||
var source = | ||
@"#nullable enable | ||
class Program | ||
{ | ||
const object? C1 = null; | ||
const object? C2 = null; | ||
#nullable enable | ||
static object F1() => C1; | ||
#nullable disable | ||
static object F2() => C2; | ||
}"; | ||
|
||
// https://github.com/dotnet/roslyn/issues/49746: Currently, if we analyze any members, we analyze all. | ||
var expectedAnalyzedKeysAll = new[] { ".cctor", ".ctor", "F1", "F2" }; | ||
|
||
verify(parseOptions: TestOptions.Regular, expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", null), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "true"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "false")); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "TRUE"), expectedAnalyzedKeysAll); // unrecognized value (incorrect case) treated as "true" | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "FALSE"), expectedAnalyzedKeysAll); // unrecognized value (incorrect case) treated as "true" | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "unknown"), expectedAnalyzedKeysAll); // unrecognized value treated as "true" | ||
verify(parseOptions: TestOptions.Regular.WithFeature("run-nullable-analysis", "true"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("run-nullable-analysis", "false"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "false").WithFeature("run-nullable-analysis", "false")); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "false").WithFeature("run-nullable-analysis", "true"), "F1", "F2"); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "true").WithFeature("run-nullable-analysis", "false"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "true").WithFeature("run-nullable-analysis", "true"), expectedAnalyzedKeysAll); | ||
|
||
void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) | ||
{ | ||
var comp = CreateCompilation(source, parseOptions: parseOptions); | ||
comp.NullableAnalysisData = new ConcurrentDictionary<object, int>(); | ||
if (expectedAnalyzedKeys.Length > 0) | ||
{ | ||
comp.VerifyDiagnostics( | ||
// (7,27): warning CS8603: Possible null reference return. | ||
// static object F1() => C1; | ||
Diagnostic(ErrorCode.WRN_NullReferenceReturn, "C1").WithLocation(7, 27)); | ||
} | ||
else | ||
{ | ||
comp.VerifyDiagnostics(); | ||
} | ||
|
||
var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); | ||
AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); | ||
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.
Is this order sensitive? #Closed 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. |
||
} | ||
} | ||
|
||
[Fact] | ||
public void NullableAnalysisFlags_02() | ||
{ | ||
var source = | ||
@"#nullable enable | ||
class Program | ||
{ | ||
const object? C1 = null; | ||
const object? C2 = null; | ||
#nullable enable | ||
static void F1(object obj = C1) { } | ||
#nullable disable | ||
static void F2(object obj = C2) { } | ||
}"; | ||
|
||
// https://github.com/dotnet/roslyn/issues/49746: Currently, if we analyze any members, we analyze all. | ||
var expectedAnalyzedKeysAll = new[] { ".cctor", ".ctor", "= C1", "= C2", "F1", "F2" }; | ||
|
||
verify(parseOptions: TestOptions.Regular, expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", null), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "true"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "false")); | ||
|
||
void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) | ||
{ | ||
var comp = CreateCompilation(source, parseOptions: parseOptions); | ||
comp.NullableAnalysisData = new ConcurrentDictionary<object, int>(); | ||
if (expectedAnalyzedKeys.Length > 0) | ||
{ | ||
comp.VerifyDiagnostics( | ||
// (7,33): warning CS8625: Cannot convert null literal to non-nullable reference type. | ||
// static void F1(object obj = C1) { } | ||
Diagnostic(ErrorCode.WRN_NullAsNonNullable, "C1").WithLocation(7, 33)); | ||
} | ||
else | ||
{ | ||
comp.VerifyDiagnostics(); | ||
} | ||
|
||
var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); | ||
AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); | ||
} | ||
} | ||
|
||
[Fact] | ||
public void NullableAnalysisFlags_03() | ||
{ | ||
var sourceA = | ||
@"#nullable enable | ||
public class A : System.Attribute | ||
{ | ||
public A(object obj) { } | ||
public const object? C1 = null; | ||
public const object? C2 = null; | ||
}"; | ||
var refA = CreateCompilation(sourceA).EmitToImageReference(); | ||
|
||
var sourceB = | ||
@"#nullable enable | ||
[A(A.C1)] | ||
struct B1 | ||
{ | ||
} | ||
#nullable disable | ||
[A(A.C2)] | ||
struct B2 | ||
{ | ||
}"; | ||
|
||
// https://github.com/dotnet/roslyn/issues/49746: Currently, if we analyze any members, we analyze all. | ||
var expectedAnalyzedKeysAll = new[] { ".cctor", ".cctor", "A(A.C1)", "A(A.C2)" }; | ||
|
||
verify(parseOptions: TestOptions.Regular, expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", null), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "true"), expectedAnalyzedKeysAll); | ||
verify(parseOptions: TestOptions.Regular.WithFeature("nullableAnalysis", "false")); | ||
|
||
void verify(CSharpParseOptions parseOptions, params string[] expectedAnalyzedKeys) | ||
{ | ||
var comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: parseOptions); | ||
comp.NullableAnalysisData = new ConcurrentDictionary<object, int>(); | ||
if (expectedAnalyzedKeys.Length > 0) | ||
{ | ||
comp.VerifyDiagnostics( | ||
// (2,4): warning CS8625: Cannot convert null literal to non-nullable reference type. | ||
// [A(A.C1)] | ||
Diagnostic(ErrorCode.WRN_NullAsNonNullable, "A.C1").WithLocation(2, 4)); | ||
} | ||
else | ||
{ | ||
comp.VerifyDiagnostics(); | ||
} | ||
|
||
var actualAnalyzedKeys = GetNullableDataKeysAsStrings(comp.NullableAnalysisData); | ||
AssertEx.Equal(expectedAnalyzedKeys, actualAnalyzedKeys); | ||
} | ||
} | ||
|
||
private static string[] GetNullableDataKeysAsStrings(ConcurrentDictionary<object, int> nullableData) => | ||
nullableData.Keys.Select(key => GetNullableDataKeyAsString(key)).OrderBy(key => key).ToArray(); | ||
|
||
private static string GetNullableDataKeyAsString(object key) => | ||
key is Symbol symbol ? symbol.MetadataName : ((SyntaxNode)key).ToString(); | ||
} | ||
} |
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.
I think it would be good to have a comment that spells the rules out. #Closed