-
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
Nullable constraint checking outside method bodies #33261
Conversation
This makes nullability an explicit parameter to constraint checking rather than inferring it from the `ConversionsBase` argument. The reason is that these really represent separate operations: checking nullability in conversions and validating nullability in constraint checking.
The work for generic `unmanaged struct` constraints ended up threading through the correct `Compilation` object in many of the cases where I was using `Compilation` to calculate whether or not nullability needed to be checked for constraints. Used that whenever possible.
Don't need to be allocating them so many times during constraint checking.
@dotnet/roslyn-compiler for review |
@@ -384,6 +384,7 @@ private bool FailsConstraintChecks(MethodSymbol method, out ArrayBuilder<TypePar | |||
bool constraintsSatisfied = ConstraintsHelper.CheckMethodConstraints( | |||
method, | |||
this.Conversions, | |||
this.Conversions.IncludeNullability, |
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.Conversions.IncludeNullability, [](start = 16, length = 36)
Should we always pass false
here? Do we ever perform overload resolution during nullable flow analysis? #Closed
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.
@@ -974,7 +974,7 @@ private void ReportDuplicateNamedArgument(MemberResolutionResult<TMember> result | |||
// formal parameter type. | |||
|
|||
TypeSymbol formalParameterType = method.ParameterTypes[result.Result.BadParameter].TypeSymbol; | |||
formalParameterType.CheckAllConstraints((CSharpCompilation)compilation, conversions, location, diagnostics); | |||
formalParameterType.CheckAllConstraints((CSharpCompilation)compilation, conversions, conversions.IncludeNullability, location, diagnostics); |
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.
conversions.IncludeNullability [](start = 97, length = 30)
Should we always pass false here? Do we ever perform overload resolution during nullable flow analysis? #Closed
@@ -3177,6 +3177,7 @@ private void CheckMethodConstraints(SyntaxNode syntax, MethodSymbol method) | |||
ConstraintsHelper.CheckMethodConstraints( | |||
method, | |||
_conversions, | |||
_conversions.IncludeNullability, |
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.
_conversions.IncludeNullability [](start = 16, length = 31)
Should we simply pass true
here? #Closed
@@ -797,6 +821,52 @@ private static bool HasDuplicateInterfaces(NamedTypeSymbol type, ConsList<TypeSy | |||
skipParameters); | |||
} | |||
|
|||
private struct ConversionsUtil |
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.
private struct ConversionsUtil [](start = 8, length = 30)
It looks like this mechanism is redundant. Does it duplicate what ConversionsBase._lazyOtherNullability
is used for? #Closed
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 there a meaningful change in this file? Consider reverting otherwise. #Closed Refers to: src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs:15 in 9a0b8cb. [](commit_id = 9a0b8cb, deletion_comment = False) |
@@ -668,6 +668,7 @@ protected TypeSymbolWithAnnotations BindEventType(Binder binder, TypeSyntax type | |||
internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics) | |||
{ | |||
var location = this.Locations[0]; | |||
bool includeNullability = DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); |
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.
bool includeNullability = DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); [](start = 12, length = 109)
It looks like the local is not used. #Closed
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.
Missed that when resolving the conflicts with Rikki PR
In reply to: 255382462 [](ancestors = 255382462)
@@ -1408,6 +1408,7 @@ internal override void ForceComplete(SourceLocation locationOpt, CancellationTok | |||
{ | |||
var diagnostics = DiagnosticBag.GetInstance(); | |||
var conversions = new TypeConversions(this.ContainingAssembly.CorLibrary); | |||
bool includeNullability = DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); |
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.
bool includeNullability = DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); [](start = 32, length = 109)
It looks like the local is not used. #Closed
@@ -106,7 +106,7 @@ internal sealed class TupleTypeSymbol : WrappedNamedTypeSymbol | |||
var constructedType = Create(underlyingType, elementNames, errorPositions, locationOpt, elementLocations); | |||
if (shouldCheckConstraints && diagnostics != null) | |||
{ | |||
constructedType.CheckConstraints(compilation.Conversions, syntax, elementLocations, compilation, diagnostics); | |||
constructedType.CheckConstraints(compilation.Conversions, compilation.Conversions.IncludeNullability, syntax, elementLocations, compilation, diagnostics); |
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.
compilation.Conversions.IncludeNullability [](start = 74, length = 42)
Should we simply pass false
here? #Closed
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.
Don't believe so. The code goes through the process of validating constraints here hence it woudl seem that nullable should be checked as well (as it was before my change)., Is the expectation that the nullable constraints are always checked elsewhere, say NullableWalker, for method bodies?
In reply to: 255382791 [](ancestors = 255382791)
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.
@AlekseyTs: @cston and I have chatted about this and I'm a bit stuck on how to proceed.
On one hand the compiler tends to special case tuple and it's questionable if tuple constraints should matter at all. But the code clearly has the intent of validating tuple constaints. Hence it seems that we should consider tuple constraints in this scenario and I can't see an obvious reason for excluding nullability ones.
As an experiment I passed false
as you suggested and no tests failed. But that possibly means we're just not validating the correct scenario here. A test where the tuple had say a class
constraint for every type parameter could just not be present to catch this.
Can you elaborate a bit on why you think false
is the right check here?
#Resolved
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.
Don't believe so. The code goes through the process of validating constraints here hence it woudl seem that nullable should be checked as well (as it was before my change)., Is the expectation that the nullable constraints are always checked elsewhere, say NullableWalker, for method bodies?
I doubt nullable aspect of constraints was checked here before. Is compilation.Conversions
ever checks nullable? I think that your intuition is probably correct that for some cases nullbility aspect should be checked here, but probably not for all cases. For example, when this method is called from BindTupleExpression
, it feels like the check for the nullability aspect should be postponed until nullable analysis, when we know nullability of expressions involved. Deconstruction is the other example. It feels like the change in this function should be scenario and unit-test driven. It would be fine to follow up on this separately.
In reply to: 255648877 [](ancestors = 255648877,255382791)
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 you elaborate a bit on why you think
false
is the right check here?
As I mentioned in my other response to this thread, I believe that would be consistent with the previous behavior. This method is never called from nullable analysis. I think that it would be good to back this change with targeted unit-tests that cover every call site for this function.
In reply to: 255666778 [](ancestors = 255666778)
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 doubt nullable aspect of constraints was checked here before. Is compilation.Conversions ever checks nullable?
This is the root of my confusion. I had mistakenly believed that compliation.Conversions.IncludeNullability
was toggled based on C# 8.0 Instead looking now that is a case where it is essentially always false
. Agree false
is correct here.
. It feels like the change in this function should be scenario and unit-test driven.
Agree. Want to make sure I get this tested. Prefer to just take care of it now vs. waiting till later if possible.
In reply to: 255676114 [](ancestors = 255676114,255648877,255382791)
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.
Dug into this a bit and decided to pull out this validation to a separate PR. The validation of tuple constraints is now done properly in method signatures, which is the intent of this PR. The problem is the validation within method bodies. Will follow up on that separately
In reply to: 255689761 [](ancestors = 255689761,255676114,255648877,255382791)
@@ -54412,6 +54684,9 @@ public void Test2(TB1 a2, TB2 b2) | |||
var comp1 = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); | |||
// https://github.com/dotnet/roslyn/issues/29678: Constraint violations are not reported for type references outside of method bodies. |
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.
// #29678: Constraint violations are not reported for type references outside of method bodies. [](start = 12, length = 134)
Consider removing this comment. #Closed
@@ -56341,12 +56635,24 @@ class C4<T, U> : I<T?>, I<U?> where T : class where U : class { }"; | |||
// (2,7): error CS0695: 'C1<T, U>' cannot implement both 'I<T>' and 'I<U>' because they may unify for some type parameter substitutions | |||
// class C1<T, U> : I<T>, I<U> where T : class where U : class { } | |||
Diagnostic(ErrorCode.ERR_UnifyingInterfaceInstantiations, "C1").WithArguments("C1<T, U>", "I<T>", "I<U>").WithLocation(2, 7), | |||
// (3,7): warning CS8634: The type 'U?' cannot be used as type parameter 'T' in the generic type or method 'I<T>'. Nullability of type argument 'U?' doesn't match 'class' constraint. |
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.
// (3,7): warning CS8634: The type 'U?' cannot be used as type parameter 'T' in the generic type or method 'I'. Nullability of type argument 'U?' doesn't match 'class' constraint. [](start = 16, length = 182)
Alternatively, we could relax class
constraint in interface I<T> where T : class
to class?
. This should reduce the unnecessary noise in the base-line. #Closed
Done with review pass (iteration 6). #Closed |
There was originally but all of the meaningful changes got removed during merge conflict resolution. Let me try and rebase this out. In reply to: 462221392 [](ancestors = 462221392) Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs:19 in 9a0b8cb. [](commit_id = 9a0b8cb, deletion_comment = False) |
There was originally but all of the meaningful changes got removed during merge conflict resolution. Let me try and rebase this out. In reply to: 462221157 [](ancestors = 462221157) Refers to: src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs:15 in 9a0b8cb. [](commit_id = 9a0b8cb, deletion_comment = False) |
I've tried to undo this but cannot. The changes mixing with the merge conflicts are making it hard to yank this from the change. In reply to: 462452107 [](ancestors = 462452107,462221157) Refers to: src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs:15 in 9a0b8cb. [](commit_id = 9a0b8cb, deletion_comment = False) |
I've tried to undo this but cannot. The changes mixing with the merge conflicts are making it hard to yank this from the change. I will remove the meaningless change from this file though. In reply to: 462449955 [](ancestors = 462449955,462221392) Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Bases.cs:19 in 9a0b8cb. [](commit_id = 9a0b8cb, deletion_comment = False) |
// (19,25): warning CS8634: The type 'object?' cannot be used as type parameter 'T' in the generic type or method 'C<T>'. Nullability of type argument 'object?' doesn't match 'class' constraint. | ||
// delegate C<object?> D(C<object?> p); // 8 | ||
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "object?").WithArguments("C<T>", "T", "object?").WithLocation(19, 25)); | ||
} |
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.
} [](start = 8, length = 1)
Are we testing constructors and operators? For instance:
#pragma warning disable 0649
namespace System
{
struct ValueTuple<T, U> where T : object
{
public T Item1;
public U Item2;
}
}
class C
{
C((object?, object?) t)
{
}
}
Done with review pass (iteration 7). I think the issue around the tuple types needs a follow-up, doesn't have to be done in this PR though. #Closed |
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 (iteration 7)
@@ -10,8 +10,8 @@ namespace Microsoft.CodeAnalysis.CSharp | |||
{ | |||
internal sealed class TypeConversions : ConversionsBase | |||
{ | |||
public TypeConversions(AssemblySymbol corLibrary) | |||
: this(corLibrary, currentRecursionDepth: 0, includeNullability: false, otherNullabilityOpt: null) | |||
public TypeConversions(AssemblySymbol corLibrary, bool includeNullability = 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.
bool includeNullability = false [](start = 58, length = 31)
Is this change needed?
@@ -575,8 +575,7 @@ internal override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedW | |||
internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics) | |||
{ | |||
var options = (CSharpParseOptions)SyntaxTree.Options; |
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.
options [](start = 16, length = 7)
Not used.
@@ -55997,9 +56271,22 @@ class B4<T> : A<T?, T?>, I<T?, T?> | |||
{ | |||
}"; |
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.
} [](start = 0, length = 1)
Consider adding:
class B5<T> : A<T, T>, I<T, T>
where T : class?
{
}
public static implicit operator C<string?>(D2 D2) => new C<string?>(); // 2, 3 | ||
} | ||
"; | ||
var compilation = CreateCompilation(Tuple2NonNullable + source, targetFramework: TargetFramework.Mscorlib46); |
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.
Tuple2NonNullable [](start = 48, length = 17)
Not used.
void M4(C<(string, string)> p) { } | ||
} | ||
"; | ||
var compilation = CreateCompilation(Tuple2NonNullable + source, targetFramework: TargetFramework.Mscorlib46); |
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.
Tuple2NonNullable + source [](start = 48, length = 26)
Consider using new[] { Tuple2NonNullable, source }
here and below.
Our constraint validation around nullability was only being done properly inside method bodies. This implements the checking in all other places: method signatures, base type lists, using static, etc ...
closes #32953