-
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
Fix nullability check in override/implementation #49723
Changes from 8 commits
c3db802
62313f7
8940d30
d5617ae
e71faba
3bd5caa
147c3e0
322f89a
dbfb16b
df7ea75
b71ca77
28608b6
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 |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
#nullable disable | ||
|
||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
using Roslyn.Utilities; | ||
|
||
|
@@ -25,37 +26,55 @@ internal static class NullableAnnotationExtensions | |
/// Join nullable annotations from the set of lower bounds for fixing a type parameter. | ||
/// This uses the covariant merging rules. (Annotated wins over Oblivious which wins over NotAnnotated) | ||
/// </summary> | ||
public static NullableAnnotation Join(this NullableAnnotation a, NullableAnnotation b) => (a < b) ? b : a; | ||
public static NullableAnnotation Join(this NullableAnnotation a, NullableAnnotation b) | ||
{ | ||
Debug.Assert(a != NullableAnnotation.Ignored); | ||
Debug.Assert(b != NullableAnnotation.Ignored); | ||
return (a < b) ? b : a; | ||
} | ||
|
||
/// <summary> | ||
/// Meet two nullable annotations for computing the nullable annotation of a type parameter from upper bounds. | ||
/// This uses the contravariant merging rules. (NotAnnotated wins over Oblivious which wins over Annotated) | ||
/// </summary> | ||
public static NullableAnnotation Meet(this NullableAnnotation a, NullableAnnotation b) => (a < b) ? a : b; | ||
public static NullableAnnotation Meet(this NullableAnnotation a, NullableAnnotation b) | ||
{ | ||
Debug.Assert(a != NullableAnnotation.Ignored); | ||
Debug.Assert(b != NullableAnnotation.Ignored); | ||
return (a < b) ? a : b; | ||
} | ||
|
||
/// <summary> | ||
/// Return the nullable annotation to use when two annotations are expected to be "compatible", which means | ||
/// they could be the same. These are the "invariant" merging rules. (NotAnnotated wins over Annotated which wins over Oblivious) | ||
/// </summary> | ||
public static NullableAnnotation EnsureCompatible(this NullableAnnotation a, NullableAnnotation b) => | ||
(a, b) switch | ||
public static NullableAnnotation EnsureCompatible(this NullableAnnotation a, NullableAnnotation b) | ||
{ | ||
Debug.Assert(a != NullableAnnotation.Ignored); | ||
Debug.Assert(b != NullableAnnotation.Ignored); | ||
return (a, b) switch | ||
{ | ||
(NullableAnnotation.Oblivious, _) => b, | ||
(_, NullableAnnotation.Oblivious) => a, | ||
_ => a < b ? a : b, | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// Merges nullability. | ||
/// </summary> | ||
public static NullableAnnotation MergeNullableAnnotation(this NullableAnnotation a, NullableAnnotation b, VarianceKind variance) => | ||
variance switch | ||
public static NullableAnnotation MergeNullableAnnotation(this NullableAnnotation a, NullableAnnotation b, VarianceKind variance) | ||
{ | ||
Debug.Assert(a != NullableAnnotation.Ignored); | ||
Debug.Assert(b != NullableAnnotation.Ignored); | ||
return variance switch | ||
{ | ||
VarianceKind.In => a.Meet(b), | ||
VarianceKind.Out => a.Join(b), | ||
VarianceKind.None => a.EnsureCompatible(b), | ||
_ => throw ExceptionUtilities.UnexpectedValue(variance) | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// The attribute (metadata) representation of <see cref="NullableAnnotation.NotAnnotated"/>. | ||
|
@@ -108,13 +127,18 @@ internal static CodeAnalysis.NullableAnnotation ToPublicAnnotation(this TypeWith | |
internal static CodeAnalysis.NullableAnnotation ToPublicAnnotation(TypeSymbol type, NullableAnnotation annotation) => | ||
annotation switch | ||
{ | ||
CSharp.NullableAnnotation.Annotated => CodeAnalysis.NullableAnnotation.Annotated, | ||
CSharp.NullableAnnotation.NotAnnotated => CodeAnalysis.NullableAnnotation.NotAnnotated, | ||
NullableAnnotation.Annotated => CodeAnalysis.NullableAnnotation.Annotated, | ||
NullableAnnotation.NotAnnotated => CodeAnalysis.NullableAnnotation.NotAnnotated, | ||
|
||
// A value type may be oblivious or not annotated depending on whether the type reference | ||
// is from source or metadata. (Binding using the #nullable context only when setting the annotation | ||
// to avoid checking IsValueType early.) The annotation is normalized here in the public API. | ||
CSharp.NullableAnnotation.Oblivious when type.IsValueType => CodeAnalysis.NullableAnnotation.NotAnnotated, | ||
CSharp.NullableAnnotation.Oblivious => CodeAnalysis.NullableAnnotation.None, | ||
NullableAnnotation.Oblivious when type.IsValueType => CodeAnalysis.NullableAnnotation.NotAnnotated, | ||
NullableAnnotation.Oblivious => CodeAnalysis.NullableAnnotation.None, | ||
|
||
// May occur as type argument in symbols in diagnostic | ||
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.
I think we should be able to avoid this if we use ConstructedFrom for diagnostic instead. #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. I think we should really make an effort to not leak a symbol with an annotation like that. Otherwise, equality is likely to misbehave, etc. In reply to: 536200456 [](ancestors = 536200456) |
||
NullableAnnotation.Ignored when type.IsTypeParameter() => CodeAnalysis.NullableAnnotation.None, | ||
|
||
_ => throw ExceptionUtilities.UnexpectedValue(annotation) | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,11 @@ internal static ImmutableArray<TypeWithAnnotations> TypeParametersAsTypeSymbolsW | |
return typeParameters.SelectAsArray((tp) => TypeWithAnnotations.Create(tp)); | ||
} | ||
|
||
internal static ImmutableArray<TypeWithAnnotations> TypeParametersAsTypeSymbolsWithIgnoredAnnotations(ImmutableArray<TypeParameterSymbol> typeParameters) | ||
{ | ||
return typeParameters.SelectAsArray((tp) => TypeWithAnnotations.Create(tp, NullableAnnotation.Ignored)); | ||
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. nit: |
||
} | ||
|
||
internal static ImmutableArray<TypeSymbol> AsTypeSymbols(ImmutableArray<TypeWithAnnotations> typesOpt) | ||
{ | ||
return typesOpt.IsDefault ? default : typesOpt.SelectAsArray(AsTypeSymbol); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1770,7 +1770,7 @@ internal static void CheckNullableReferenceTypeMismatchOnImplementingMember(Type | |
|
||
if (implementedMethod.IsGenericMethod) | ||
{ | ||
implementedMethod = implementedMethod.Construct(implementingMethod.TypeArgumentsWithAnnotations); | ||
implementedMethod = implementedMethod.Construct(TypeMap.TypeParametersAsTypeSymbolsWithIgnoredAnnotations(implementingMethod.TypeParameters)); | ||
} | ||
|
||
SourceMemberContainerTypeSymbol.CheckValidNullableMethodOverride( | ||
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 feels like the code in this function should also dig to constructed from as appropriate. #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. The lambdas that are passed in do use ConstrcutedFrom (actually, they don't need to dig into derived/implementating method, I was hasty and will revert that part). The rest of the method is fine (see earlier comment). In reply to: 536304396 [](ancestors = 536304396) |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,11 +161,6 @@ internal bool CanBeAssignedNull | |
} | ||
} | ||
|
||
private static bool IsIndexedTypeParameter(TypeSymbol typeSymbol) | ||
{ | ||
return typeSymbol is IndexedTypeParameterSymbol; | ||
} | ||
|
||
private static TypeWithAnnotations CreateNonLazyType(TypeSymbol typeSymbol, NullableAnnotation nullableAnnotation, ImmutableArray<CustomModifier> customModifiers) | ||
{ | ||
return new TypeWithAnnotations(typeSymbol, nullableAnnotation, Extensions.Create(customModifiers)); | ||
|
@@ -429,6 +424,9 @@ public TypeWithAnnotations SubstituteType(AbstractTypeMap typeMap) => | |
|
||
internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap) | ||
{ | ||
// Ignored may only appear on a replacement type and will not survive the substitution (ie. the original annotation wins over Ignored) | ||
Debug.Assert(NullableAnnotation != NullableAnnotation.Ignored); | ||
|
||
var newCustomModifiers = typeMap.SubstituteCustomModifiers(this.CustomModifiers); | ||
TypeSymbol typeSymbol = this.Type; | ||
var newTypeWithModifiers = typeMap.SubstituteType(typeSymbol); | ||
|
@@ -437,6 +435,7 @@ internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap) | |
{ | ||
Debug.Assert(newTypeWithModifiers.NullableAnnotation.IsOblivious() || (typeSymbol.IsNullableType() && newTypeWithModifiers.NullableAnnotation.IsAnnotated())); | ||
Debug.Assert(newTypeWithModifiers.CustomModifiers.IsEmpty); | ||
Debug.Assert(NullableAnnotation != NullableAnnotation.Ignored); | ||
|
||
if (typeSymbol.Equals(newTypeWithModifiers.Type, TypeCompareKind.ConsiderEverything) && | ||
newCustomModifiers == CustomModifiers) | ||
|
@@ -457,7 +456,7 @@ internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap) | |
{ | ||
return this; // substitution had no effect on the type or modifiers | ||
} | ||
else if (Is((TypeParameterSymbol)typeSymbol)) | ||
else if (Is((TypeParameterSymbol)typeSymbol) && newTypeWithModifiers.NullableAnnotation != NullableAnnotation.Ignored) | ||
{ | ||
return newTypeWithModifiers; | ||
} | ||
|
@@ -468,14 +467,13 @@ internal TypeWithAnnotations SubstituteTypeCore(AbstractTypeMap typeMap) | |
} | ||
|
||
NullableAnnotation newAnnotation; | ||
|
||
Debug.Assert(!IsIndexedTypeParameter(newTypeWithModifiers.Type) || newTypeWithModifiers.NullableAnnotation.IsOblivious()); | ||
Debug.Assert(newTypeWithModifiers.Type is not IndexedTypeParameterSymbol || newTypeWithModifiers.NullableAnnotation == NullableAnnotation.Ignored); | ||
|
||
if (NullableAnnotation.IsAnnotated() || newTypeWithModifiers.NullableAnnotation.IsAnnotated()) | ||
{ | ||
newAnnotation = NullableAnnotation.Annotated; | ||
} | ||
else if (IsIndexedTypeParameter(newTypeWithModifiers.Type)) | ||
else if (newTypeWithModifiers.NullableAnnotation == NullableAnnotation.Ignored) | ||
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 looks like we already have a special handling for indexed type parameters that has the same effect. If that is the case, I think we should reuse this mechanism rather than going through the trouble of dealing with the new annotation. We might need to use ConstructedFrom in diagnostics in affected components though. #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. We can't use indexed type parameters because (1) they don't have names, and (2) they don't have constraints (so the logic in 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.
I would like to get a better understanding of these issues. Let's sync offline In reply to: 535501474 [](ancestors = 535501474) 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. Would it be possible to remove the special handling for indexed type parameters and use the new annotation instead? In reply to: 535509257 [](ancestors = 535509257,535501474) 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.
Interesting idea. Thanks. Pushed a commit for that change #Resolved |
||
{ | ||
newAnnotation = NullableAnnotation; | ||
} | ||
|
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 probably provide details about how exactly it is ignored. So that one could predict the impact of usage of this annotation #Closed