-
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
Support nullable annotations on unconstrained type parameters #45993
Support nullable annotations on unconstrained type parameters #45993
Conversation
c22cd86
to
eb3bf46
Compare
4b2e7a2
to
378b787
Compare
src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbol.cs
Show resolved
Hide resolved
@@ -53,7 +53,7 @@ public static bool CanBeConst(this TypeSymbol typeSymbol) | |||
/// T where T : IComparable => true | |||
/// T where T : IComparable? => true | |||
/// </summary> | |||
public static bool IsTypeParameterDisallowingAnnotation(this TypeSymbol type) | |||
public static bool IsTypeParameterDisallowingAnnotation(this TypeSymbol type) // The method name is misleading since annotations are allowed always. |
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.
Think we should move this comment to a <remarks>
section. #Resolved
/// </summary> | ||
public void TryForceResolveAsNullableValueType() | ||
public void TryForceResolve(bool asValueTypeNotReferenceType) |
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 asValueType
. #Resolved
? NullableAnnotation.NotAnnotated : NullableAnnotation.Annotated; | ||
if (Type is null) | ||
{ | ||
return TypeWithAnnotations.Create(Type, NullableAnnotation.Annotated); |
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.
return TypeWithAnnotations.Create(Type, NullableAnnotation.Annotated); | |
return TypeWithAnnotations.Create(null, NullableAnnotation.Annotated); |
Why do we unconditionally use NullableAnnotation.Annotated
here for null
types? #Resolved
src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs
Show resolved
Hide resolved
var formatWithNullableModifier = formatWithoutModifiers.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); | ||
var formatWithBothModifiers = formatWithNullableModifier.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNotNullableReferenceTypeModifier); | ||
|
||
verify("C.F0", "T F0<T>()", "T F0<T>()", "T F0<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.
Why is the final one not T! F0<T>()
? #ByDesign
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 behavior of SymbolDisplayMiscellaneousOptions.IncludeNotNullableReferenceTypeModifier
is unchanged. The type could be reported as T!
here, and in T F8<T>() where T : notnull
, independent of this PR.
In reply to: 455921736 [](ancestors = 455921736)
|
||
if (Type is null) | ||
{ | ||
return NullableAnnotation.NotAnnotated; |
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 do we prefer NotAnnotated
here over Oblivious
? #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.
I've removed the explicit Type is null
case so the behavior should match the current behavior which returns this.NullableAnnotation
. That said, I suspect NullableAnnotation
is typically NotAnnotated
when Type is null
since that is the default
value for the struct
.
In reply to: 456081013 [](ancestors = 456081013)
[InlineData(false)] | ||
[InlineData(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.
[InlineData(false)] | |
[InlineData(true)] | |
[CombinatorialData] | |
``` #Resolved |
@@ -5603,7 +5603,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
<value>Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed.</value> | |||
</data> | |||
<data name="ERR_NullableUnconstrainedTypeParameter" xml:space="preserve"> | |||
<value>A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.</value> | |||
<value>A nullable type parameter must be known to be a value type or non-nullable reference type. Please use language version '{0}' or greater, or consider adding a 'class', 'struct', or type constraint.</value> |
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 use language version '{0}' [](start = 102, length = 34)
I think we should simply use regular language version message with specific feature name, etc. This way IDE will be able to suggest the change language version fix. #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.
This message has helpful suggestions for using T?
in C#8 which the general language version message would not. We can update the IDE to recognize this ErrorCode
if necessary.
In reply to: 456600909 [](ancestors = 456600909)
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.
@@ -5731,6 +5731,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
<data name="IDS_FeatureNullPointerConstantPattern" xml:space="preserve"> | |||
<value>null pointer constant pattern</value> | |||
</data> | |||
<data name="IDS_FeatureDefaultTypeParameterConstraint" xml:space="preserve"> | |||
<value>default type parameter constraints</value> |
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.
default type parameter constraints [](start = 11, length = 34)
It feels like the name of the feature isn't very clear, especially that default
is used in not very common cases. For example, a user typed T?
and compiler reports that "default type parameter constraints" aren't supported by the language version. What constraints the message is talking about? This isn't a constraint. #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.
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 message is only reported for uses of where T : default
. For uses of T?
, we continue to report ErrorCode.ERR_NullableUnconstrainedTypeParameter
.
In reply to: 456603253 [](ancestors = 456603253,456602711)
@@ -1908,6 +1909,9 @@ private TypeParameterConstraintSyntax ParseTypeParameterConstraint() | |||
questionToken = this.TryEatToken(SyntaxKind.QuestionToken); | |||
|
|||
return _syntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint, classToken, questionToken); | |||
case SyntaxKind.DefaultKeyword: | |||
var defaultToken = this.EatToken(); | |||
return _syntaxFactory.DefaultConstraint(defaultToken); |
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.
_syntaxFactory.DefaultConstraint(defaultToken) [](start = 27, length = 46)
Should we check language version 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.
The version check is in BindTypeParameterConstraints()
.
In reply to: 456659327 [](ancestors = 456659327)
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 version check is in BindTypeParameterConstraints().
I realized that. The question is why? Usually, for simple cases like that we perform language version checks in the parser.
In reply to: 456693574 [](ancestors = 456693574,456659327)
@@ -334,6 +335,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) | |||
case MessageID.IDS_FeatureRecords: | |||
case MessageID.IDS_FeatureStaticAnonymousFunction: // syntax check | |||
case MessageID.IDS_FeatureModuleInitializers: // semantic check on method attribute | |||
case MessageID.IDS_FeatureDefaultTypeParameterConstraint: |
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.
case MessageID.IDS_FeatureDefaultTypeParameterConstraint: [](start = 16, length = 57)
Consider adding a comment if this is not a parsing time check #Closed
{ | ||
if (type.Type.IsTypeParameterDisallowingAnnotation()) | ||
{ | ||
var requiredVersion = MessageID.IDS_FeatureDefaultTypeParameterConstraint.RequiredVersion(); |
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.
IDS_FeatureDefaultTypeParameterConstraint [](start = 48, length = 41)
This is somewhat confusing because default
constraint isn't involved. #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.
private static bool IsPossiblyNullableTypeTypeParameter(in TypeWithAnnotations typeWithAnnotations) | ||
{ | ||
var type = typeWithAnnotations.Type; | ||
return type is object && |
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.
return type is object && [](start = 12, length = 24)
It looks like we dropped IsNotAnnotated
check. Is this intentional? Could you please elaborate? #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.
It looks like we dropped IsNotAnnotated check. Is this intentional?
Yes. Callers above are explicitly checking NullableAnnotation
, and requiring NullableAnnotation == NullableAnnotation.NotAnnotated
here would mean T
and T?
would be considered identical in HasTopLevelNullabilityIdentityConversion()
. For instance, that would allow assigning IEnumerable<T?>
to IEnumerable<T>
in UnconstrainedTypeParameter_21()
.
In reply to: 456696256 [](ancestors = 456696256)
b9d17f3
to
6fff28c
Compare
440cb3b
to
280bdf4
Compare
The spanish failure is in UpdaterService which is flaky. Will filean issue shortly. Merging. |
…-pointers * upstream/master: (207 commits) Update argument state when parameter has not-null type (dotnet#46072) Fix TypeWithAnnotations.ToTypeWithState() for (untyped) null literal (dotnet#46344) Update README (dotnet#46136) Revert "Revert "Support nullable annotations on unconstrained type parameters"" Revert "Support nullable annotations on unconstrained type parameters (dotnet#45993)" Fix type in publish data Update VSIXExpInstaller version to one available on ADO Update publish data for 16.8 Update version of RichCodeNav.EnvVarDump A fixed initializer must be bound to its natural type (dotnet#46293) Update features merged into 16.7p4 (dotnet#46229) Async-streams: disposal should continue without jump within a finally (dotnet#46188) Recommend default in type constraint, but not record (dotnet#46311) Add use site diagnostics to IsUnmanaged (dotnet#46114) Add another flaky test. Ensure NuGet connections use TLS 1.2 Update to Microsoft.CodeAnalysis.Testing 1.0.1-beta1.20374.2 Skip flaky test. Fix build break. (dotnet#46303) Skip a flaky test Relates to dotnet#46304 ...
…to function-pointer-type-lookup * upstream/features/function-pointers: (212 commits) Correct public API number. Update argument state when parameter has not-null type (dotnet#46072) Fix TypeWithAnnotations.ToTypeWithState() for (untyped) null literal (dotnet#46344) Update README (dotnet#46136) Revert "Revert "Support nullable annotations on unconstrained type parameters"" Revert "Support nullable annotations on unconstrained type parameters (dotnet#45993)" Fix type in publish data Update VSIXExpInstaller version to one available on ADO Update publish data for 16.8 Update version of RichCodeNav.EnvVarDump A fixed initializer must be bound to its natural type (dotnet#46293) Update features merged into 16.7p4 (dotnet#46229) Async-streams: disposal should continue without jump within a finally (dotnet#46188) Recommend default in type constraint, but not record (dotnet#46311) Add use site diagnostics to IsUnmanaged (dotnet#46114) Add another flaky test. Ensure NuGet connections use TLS 1.2 Update to Microsoft.CodeAnalysis.Testing 1.0.1-beta1.20374.2 Skip flaky test. Fix build break. (dotnet#46303) ...
Why was T? on unconstrained types implemented as meaning ValueOrDefault instead of ValueOrNull? For ValueOrNull, we're still back to the old method of duplicating classes with struct/class constraints. |
Could you please share some specific scenarios that are negatively affected by this? |
The following code compiles but doesn't behave as one would expect. Previously it would fail to compile, now it just doesn't do what I'd expect it to.
The solution is to write it like this
This solution however requires a lot of code copy/paste, and inheritance references can get complicated, especially when the classes contain considerable code. And the syntax is weird for string: |
Support
?
annotations on unconstrained type parameter references:?
annotation is allowed for type parameters that are not constrained to reference types or value types?
annotation is an error when used on unconstrained type parameter with/langversion:8
Support
where T : default
constraint:default
constraint is allowed for method type parameters in overridden or explicitly implemented methods to disambiguate between overloads defined in the base or interfacedefault
constraint is an error with/langversion:8
default
constraint is an error when used outside of method overrides or explicit implementationsdefault
constraint is an error when the type parameter is constrainedFixes #29146