-
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
Diagnostic Improvements for error #0281 #27052
Conversation
Looks like the bootstrap is failing. |
@@ -1909,7 +1909,7 @@ private void CheckOptimisticIVTAccessGrants(DiagnosticBag bag) | |||
|
|||
if (conclusion == IVTConclusion.PublicKeyDoesntMatch) | |||
bag.Add(ErrorCode.ERR_FriendRefNotEqualToThis, NoLocation.Singleton, | |||
otherAssembly.Identity); | |||
otherAssembly.Identity.ToString(), this.ContainingAssembly.Identity.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 don't believe we should be calling otherAssembly.Identity.ToString
here. That will cause it to be captured under the current UI culture. Diagnostics can have their messages realized under a different culture.
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.
toString()
has been overridden to be culture-independent.
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.
Why use ToString()
here rather than letting the MessageProvider
serialize the arguments?
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 usually prefer to be explicit on how things get serialized to strings -- Jared's comment reflects that this is usually a very deliberate decision and being explicit signals intent better, IMHO.
@@ -63,6 +64,23 @@ public static ImmutableArray<T> AsImmutableOrNull<T>(this IEnumerable<T> items) | |||
return ImmutableArray.CreateRange<T>(items); | |||
} | |||
|
|||
public static string PublicKeyToString(this ImmutableArray<byte> key) |
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.
Probably don't want this as a general purpose extension method on ImmutableArray<byte>
. Feels pretty specific to a scenario.
|
||
foreach (ImmutableArray<byte> key in unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name)) | ||
{ | ||
friendRefNotEqualToThis = key.SequenceEqual(this.ContainingType.ContainingAssembly.Identity.PublicKey) ? false : friendRefNotEqualToThis; |
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.
indentation seems off here...
{ | ||
var unwrappedSymbols = ImmutableArray.Create<Symbol>(unwrappedSymbol); | ||
diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_BadAccess, new[] { unwrappedSymbol }, unwrappedSymbols, additionalLocations: ImmutableArray<Location>.Empty) : null; | ||
} |
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.
Consider checking diagnose
separately from each individual case. And perhaps combine all checks with ?:
:
var diagInfo = diagnose ?
inaccessibleViaQualifier ?
new CSDiagnosticInfo(...) :
friendNotEqualToThis ?
new CSDiagnosticInfo(...) :
new CSDiagnosticInfo(...) :
null;
foreach (ImmutableArray<byte> key in unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name)) | ||
{ | ||
friendRefNotEqualToThis = key.SequenceEqual(this.ContainingType.ContainingAssembly.Identity.PublicKey) ? false : friendRefNotEqualToThis; | ||
} |
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.
Can be combined with friendRefNotEqualToThis
assignment above:
bool friendRefNotEqualToThis = unwrappedSymbol != null && ... &&
unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(
this.ContainingAssembly.Name).Any((key, publicKey) =>
key.SequenceEqual(publicKey),
this.ContainingAssembly.Identity.PublicKey);
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.
That would capture this
, and capturing lambdas are prohibited in compiler code.
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.
My mistake, there is no Any
overload that takes an additional arg
parameter passed to the predicate. @agocke is correct that means the lambda would need to capture a local or this
.
@@ -1148,21 +1147,33 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio | |||
return LookupResult.Inaccessible(symbol, diagInfo); | |||
} | |||
else if (!InCref && | |||
!this.IsAccessible(unwrappedSymbol, |
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.
Looks like the old whitespace was correct
var unwrappedSymbols = ImmutableArray.Create<Symbol>(unwrappedSymbol); | ||
diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_BadAccess, new[] { unwrappedSymbol }, unwrappedSymbols, additionalLocations: ImmutableArray<Location>.Empty) : null; | ||
} | ||
bool friendRefNotEqualToThis = unwrappedSymbol != null && unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name).Any() |
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 don't think unwrappedSymbol
can be null here. It looks like it's always assigned above.
|
||
foreach (ImmutableArray<byte> key in unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name)) | ||
{ | ||
friendRefNotEqualToThis = key.SequenceEqual(this.ContainingType.ContainingAssembly.Identity.PublicKey) ? false : friendRefNotEqualToThis; |
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 would move all this logic into a local function. That way if the first if
statement is true, we don't waste any time doing unnecessary calculations.
@@ -1196,7 +1207,7 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio | |||
return LookupResult.Good(symbol); | |||
} | |||
} | |||
|
|||
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.
Accidental whitespace change.
} | ||
|
||
bool friendRefNotEqualToThis = unwrappedSymbol != null && unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name).Any() | ||
&& unwrappedSymbol.DeclaredAccessibility.Equals(Accessibility.Internal); |
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 should be able to use ==
instead of .Equals
here.
diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_BadAccess, new[] { unwrappedSymbol }, unwrappedSymbols, additionalLocations: ImmutableArray<Location>.Empty) : null; | ||
} | ||
|
||
bool friendRefNotEqualToThis = unwrappedSymbol != null && unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.ContainingType.ContainingAssembly.Name).Any() |
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.
Can ContainingAssembly.Name
be null
here?
friendRefNotEqualToThis = key.SequenceEqual(this.ContainingType.ContainingAssembly.Identity.PublicKey) ? false : friendRefNotEqualToThis; | ||
} | ||
|
||
diagInfo = diagnose ? |
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'm not sure this is what @cston had in mind. I think he just wanted the diagnose
check lifted to before the first if
statement, but I may be wrong. complete conversion to ? :
doesn't look more readable to me.
@@ -7,6 +7,7 @@ | |||
using System.IO; | |||
using System.Linq; | |||
using System.Runtime.CompilerServices; | |||
using System.Text; |
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.
Please revert the change to this file, import doesn't appear to be necessary.
@@ -58,6 +58,20 @@ public override string ToString() | |||
return GetDisplayName(fullKey: false); | |||
} | |||
|
|||
|
|||
internal static string PublicKeyToString(ImmutableArray<byte> key) |
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.
Nit: double blank line above. #Resolved
var unwrappedSymbols = ImmutableArray.Create<Symbol>(unwrappedSymbol); | ||
diagInfo = diagnose ? new CSDiagnosticInfo(ErrorCode.ERR_BadAccess, new[] { unwrappedSymbol }, unwrappedSymbols, additionalLocations: ImmutableArray<Location>.Empty) : null; | ||
} | ||
bool friendRefNotEqualToThis = getFriendRefNotEqualToThis(); |
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.
Calling it up front means we still pay for the execution. I would inline this call.
|
||
bool getFriendRefNotEqualToThis() | ||
{ | ||
if (inaccessibleViaQualifier) |
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 this check should be outside of the local funciton.
{ | ||
Debugger.Launch(); | ||
return false; | ||
} |
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.
Is this catch
block necessary?
{ | ||
try | ||
{ | ||
if (inaccessibleViaQualifier) |
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.
The caller already checks this.
&& unwrappedSymbol.DeclaredAccessibility == Accessibility.Internal | ||
&& this.Compilation.Assembly.PublicKey != null; | ||
|
||
if (temp && unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.Compilation.AssemblyName).Any()) |
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.
Is the && unwrappedSymbol...Any()
check needed?
{ | ||
foreach (ImmutableArray<byte> key in unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.Compilation.AssemblyName)) | ||
{ | ||
temp = key.SequenceEqual(this.Compilation.Assembly.Identity.PublicKey) ? false : temp; |
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.
No need to check keys beyond the first match.
if (key.SequenceEqual(this.Compilation.Assembly.Identity.PublicKey))
{
return false;
}
var friendClass = CreateCompilation(@" | ||
using System.Runtime.CompilerServices; | ||
[ assembly: InternalsVisibleTo(""cs0281, PublicKey=00240000048000009400000006020000002400005253413100040000010001002b986f6b5ea5717d35c72d38561f413e267029efa9b5f107b9331d83df657381325b3a67b75812f63a9436ceccb49494de8f574f8e639d4d26c0fcf8b0e9a1a196b80b6f6ed053628d10d027e032df2ed1d60835e5f47d32c9ef6da10d0366a319573362c821b5f8fa5abc5bb22241de6f666a85d82d6ba8c3090d01636bd2bb"") ] | ||
public class FriendClass |
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.
Should the class be named PublicClass
?
Diagnostic((ErrorCode)errorCode, "MyMethod").WithArguments("FriendClass.MyMethod()").WithLocation(7, 15) | ||
); | ||
break; | ||
default: throw new NotImplementedException(); |
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.
The InlineData
makes this test difficult to read. Could this be written more explicitly and without InlineData
? Perhaps:
var source1 = @"
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo(...)]
public class PublicClass
{
internal static void InternalMethod() { }
protected static void ProtectedMethod() { }
private static void PrivateMethod() { }
internal protected static void InternalProtectedMethod() { }
private protected static void PrivateProtectedMethod() { }
}";
var source2 = @"
class Test
{
static void Main()
{
PublicClass.InternalMethod();
PublicClass.ProtectedMethod();
...
}
}";
{ | ||
diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_BadProtectedAccess, unwrappedSymbol, accessThroughType, this.ContainingType); | ||
} | ||
else if (getFriendRefNotEqualToThis()) |
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.
We usually prefix bool
-returning functions and properties with words like Is
or Has
. I would call this function something like isBadIvtSpecification
.
// PublicClass.InternalMethod(); | ||
Diagnostic(ErrorCode.ERR_FriendRefNotEqualToThis, "InternalMethod").WithArguments("Paul, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "").WithLocation(7, 15), | ||
// (8,21): error CS0281: Friend access was granted by 'Paul, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null', but the public key of the output assembly ('') does not match that specified by the InternalsVisibleTo attribute in the granting assembly. | ||
// PublicClass.ProtectedMethod(); |
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.
This doesn't look right. protected
members are not affected by InternalsVisibleTo, so we should not provide the new error message for protected
accesses, only things that have effectively "internal" access.
); | ||
|
||
} | ||
|
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.
Since we previously hit a bug due to accessing the symbol from a using
alias, I would expect a regression test that accesses an internal symbol via a using alias, like
using MyAlias = OtherNamespace.FriendClass;
Diagnostic(ErrorCode.ERR_NoSuchMember, "PrivateMethod").WithArguments("PublicClass", "PrivateMethod").WithLocation(9, 21), | ||
// (10,21): error CS0122: 'PublicClass.InternalProtectedMethod()' is inaccessible due to its protection level | ||
// PublicClass.InternalProtectedMethod(); | ||
Diagnostic(ErrorCode.ERR_BadAccess, "InternalProtectedMethod").WithArguments("PublicClass.InternalProtectedMethod()").WithLocation(10, 21), |
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 these two are the wrong message. "protected internal" and "private protected" should be the same as "internal"
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.
LGTM assuming tests pass and minor comments are addressed
|
||
bool IsBadIvtSpecification() | ||
{ | ||
if ((unwrappedSymbol.DeclaredAccessibility == Accessibility.Internal || unwrappedSymbol.DeclaredAccessibility == Accessibility.ProtectedAndInternal |
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 would split these each onto their own line for readability, i.e.
(unwrappedSymbol.DeclaredAccessibility == Accessibility.Internal ||
unwrappedSymbol.DeclaredAccessibility == Accessibility.ProtectedAndInternal ||
unwrappedSymbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal)
if ((unwrappedSymbol.DeclaredAccessibility == Accessibility.Internal || unwrappedSymbol.DeclaredAccessibility == Accessibility.ProtectedAndInternal | ||
|| unwrappedSymbol.DeclaredAccessibility == Accessibility.ProtectedOrInternal) | ||
// Ensures that during binding we don't ask for public key which results in attribute binding and stack overflow. | ||
// If looking up attributes, don't ask for public key. |
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 these comment lines are out of order (it reads better if the lower one is at the top).
Looks like there are a few new broken tests that just need rebaselining. |
retest this please |
@dotnet-bot test windows_debug_spanish_unit32_prtest |
return LookupResult.Inaccessible(symbol, diagInfo); | ||
if (!diagnose) | ||
{ | ||
diagInfo = null; |
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.
Indent still seems off here. Currently three spaces, should be four.
} | ||
else if (IsBadIvtSpecification()) | ||
{ | ||
diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_FriendRefNotEqualToThis, unwrappedSymbol.ContainingAssembly.Identity.ToString(), AssemblyIdentity.PublicKeyToString(this.Compilation.Assembly.PublicKey)); |
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.
Indent is off here. Currently seven spaces, should be four.,
} | ||
else if (inaccessibleViaQualifier) | ||
{ | ||
diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_BadProtectedAccess, unwrappedSymbol, accessThroughType, this.ContainingType); |
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.
Indent is off here. currently six spaces, should be four.
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.
Couple minor style issues but otherwise looks good.
.VerifyDiagnostics( | ||
// (12,11): error CS0122: 'Base.Field1' is inaccessible due to its protection level | ||
|
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.
This looks wrong. There are no IVT attributes or references in this compilation.
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.
b.Field1 is trying to access a private protected field it has no access to. What error should be thrown instead?
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.
The old message looks correct here. The current one doesn't make any sense. How could a compilation provide IVT access to itself?
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.
Ok will change it back. The test passes only with the new error. How should we differentiate this from other cases?
return false; | ||
} | ||
} | ||
return true; |
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.
This returns true even if there are no IVT attributes. The check should be that there is at least one attribute, but all of the public keys are wrong.
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.
line 1221 is nested within the greater Accessibility check statement, and is only called if all of the SequenceEquals have returned false / the public keys are wrong. There is a return false at line 1223, outside of the accessibility check. Are you referring to different attributes?
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.
DeclaredAccessibility just means what it was declared as, e.g. internal
. No check on if it has IVT access.
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.
Ah, then how does one test for attributes?
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 you can just check if the thing you're going to foreach over is empty before trying and return false if so.
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.
Pushed
@@ -1204,14 +1204,16 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio | |||
|
|||
bool IsBadIvtSpecification() | |||
{ | |||
IEnumerable<ImmutableArray<byte>> keys = unwrappedSymbol.ContainingAssembly.GetInternalsVisibleToPublicKeys(this.Compilation.AssemblyName); |
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 have to do this inside the if
or, as the comment says, we'll stack overflow when binding attributes.
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.
pushed
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.
LGTM once unnecessary changes are reverted and tests pass.
@@ -266,7 +268,7 @@ void M() | |||
} | |||
} | |||
"; | |||
CreateCompilation(source, parseOptions: TestOptions.Regular7_2) | |||
CreateCompilation(source, parseOptions: TestOptions.Regular7_2, assemblyName: "Paul") |
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 you can revert all changes to this file now.
@@ -352,7 +354,7 @@ private protected class Inner // error: protected not allowed in struct | |||
} | |||
} | |||
"; | |||
CreateCompilation(source, parseOptions: TestOptions.Regular7_2) | |||
CreateCompilation(source, parseOptions: TestOptions.Regular7_2, assemblyName: "Paul") |
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.
Same here.
@@ -633,7 +633,7 @@ public class C | |||
internal class D | |||
{ | |||
static public int d_pub; | |||
}"); | |||
}", assemblyName: "Paul"); |
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.
And here.
@@ -1642,7 +1643,7 @@ public void ProtectedAndInternalNestedBaseClass() | |||
var il = @" | |||
.assembly extern mscorlib { .ver 4:0:0:0 .publickeytoken = (B7 7A 5C 56 19 34 E0 89) } | |||
|
|||
.assembly '<<GeneratedFileName>>' | |||
.assembly 'Paul' |
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.
And here.
@@ -145,7 +145,7 @@ System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globaliz | |||
> System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(""de-DE"") | |||
> ? System.Math.PI | |||
3,1415926535897931 | |||
>", runner.Console.Out.ToString()) | |||
> ", runner.Console.Out.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.
And here.
Customer scenario
What does the customer do to get into this situation, and why do we think this
is common enough to address for this release. (Granted, sometimes this will be
obvious "Open project, VS crashes" but in general, I need to understand how
common a scenario is)
When one tries to access a public method in an internal class, or when one tries to access an internal method in a public class.
Bugs this fixes
#17322
Workarounds, if any
No, diagnostic improvement.
Risk
Very low.
Performance impact
Negligible: differentiates between two error cases but does not impact performance.
Is this a regression from a previous update?
Yes, regression from native compiler.
Root cause analysis
How did we miss it? What tests are we adding to guard against it in the future?
Insufficient test coverage.
How was the bug found?
(E.g. customer reported it vs. ad hoc testing)
Marek Safar - external-ish.
Test documentation updated?
No, diagnostic improvement.
If this is a new non-compiler feature or a significant improvement to an existing feature, update https://github.com/dotnet/roslyn/wiki/Manual-Testing once you know which release it is targeting.