-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
NullabilityInfoContext reports null/notnull for unspecified generic parameters #63660
Comments
Tagging subscribers to this area: @dotnet/area-system-reflection Issue DetailsDescriptionFor a generic parameter However, the value returned seems to depend on whether the type is declared in a nullable or non-nullable context Reproduction Stepspublic static void Main()
{
var context = new NullabilityInfoContext();
var fooInfo = context.Create(typeof(Foo<string?>).GetConstructors().Single().GetParameters().Single());
Console.WriteLine(fooInfo.WriteState); // NotNull (I would expect Unknown; NotNull is clearly wrong here given that I can make Foo<string?>)
var barInfo = context.Create(typeof(Bar<string?>).GetConstructors().Single().GetParameters().Single());
Console.WriteLine(barInfo.WriteState); // Unknown
}
#nullable disable
private class Foo<T> { public Foo(T t) { } }
#nullable enable
#nullable disable
private class Bar<T> { public Bar(T t) { } }
#nullable enable Expected behaviorConsistently returns Unknown. Actual behaviorSometimes returns NotNull. Regression?No response Known WorkaroundsNo response ConfigurationVS 17.0.2, .NET 6, Windows 10 x64. I don't have any reason to believe this is configuration specific. Other informationC#9 allows for both class Generic<T> { public void Foo(T a, T? b) { } } It's unclear what On the other hand, it seems nice to be able to differentiate between the two in some way. Perhaps it makes sense to add another null state that explicitly represents the case of
|
Somewhat related issue here - see below PR just to fix |
As it relates to this issue, it appears that code was added to inspect the generic type (e.g. perhaps it was a closed type), and return the correct nullability status. But the code is so broken that the value is completely useless. It would be more useful if it did not do any inspection and simply returned the nullability flag of I have not spent the time to write a fix and put a PR here (besides the small fix above), but I have written a great many tests that demonstrate the wide variety of ways that See: |
I agree. I would say that "Unknown" should be returned for |
Therefore the nullability result of
With that being said i see
That is interesting idea |
We are doing that inspection exactly for returning the nullability flag of T given a method Foo(). (meaning, if it was declared T or T?) , but that information is not readily available, only available if it is open generic. When |
Thanks for the input. I will update my test project to match your description of C# semantics and internals. |
Based on https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references, there is zero difference between
That's why I feel it is misleading to return either NotNull or Nullable when examining a generic parameter of type T without further information. Curious whether we agree on what // classes
#nullable disable
class ObliviousGeneric<T> { T A; }
#nullable enable
class Generic<T> { T B; T? C; }
class HasGeneric { ObliviousGeneric<object> D; ObliviousGeneric<object?> E; Generic<object> F; Generic<object?> G; }
class DerivesFromObliviousNonNullGeneric : ObliviousGeneric<object> { }
class DerivesFromObliviousNullGeneric : ObliviousGeneric<object?> { }
class DerivesFromNonNullGeneric : Generic<object> { }
class DerivesFromNullGeneric : Generic<object?> { }
class NotNullGeneric<T> where T : notnull { T I; T? J; }
class ClassGeneric<T> where T : class { T K; T? L; }
class NullClassGeneric<T> where T : class? { T M; T? N; }
class StructGeneric<T> where T : struct { T O; T? P; }
class NotNullGenericDerivesFromObliviousGeneric<T> : ObliviousGeneric<T> where T : notnull { }
class DerivesFromNotNullGenericDerivesFromObliviousGeneric : NotNullGenericDerivesFromObliviousGeneric<object> { }
class ClassRestrictedGeneric<T> where T : Class1 { T K; T? L; }
class NullClassRestrictedGeneric<T> where T : Class1? { T M; T? N; }
class Class1 { }
|
This comment has been minimized.
This comment has been minimized.
@Shane32 good catch! I've updated my list and added a few more cases too. From poking around the
|
May be useful internally even if not exposed so as to not change or complicate the existing API. The use case isn't likely to be writing a new code editor, but rather simple tasks that just needs a yes/no/unknown answer. Yet, I'm not against extending the API either with a few more enums. (We could hardly say it was a breaking change considering that the existing API does not return reliable results anyway.) |
@madelson's first table looks correct, i was mistaken about the For the other table mainly looks good, i pasted below the diff between your table and the current result, looks main difference i prefer Nullable than Unknown 😆
That would be great, thanks @Shane32 ! |
Can we add these conditions and results also in the list @madelson ? class ClassRestrictedGeneric<T> where T : Class1 { T K; T? L; }
class NullClassRestrictedGeneric<T> where T : Class1? { T M; T? N; }
class Class1 { } They should match the results of these existing samples: class ClassGeneric<T> where T : class { T K; T? L; }
class NullClassGeneric<T> where T : class? { T M; T? N; } However, my experimentation shows that the compiler is incorrectly applying an 'unknown' flag in the |
@Shane32 I agree that this case is weird. The compiler does successfully warn on In contrast, for I've added these to the table. |
It’s an attribute under the generic type parameter’s type definition. But it uses the context of the parent type. Took me a while to find it also. |
But it only is being written correctly for “class” or “class?” - I think |
@Shane32 so you can find the metadata to differentiate |
Well, I can find it, but it’s set to 0 (unknown). I’ll post some code. |
var aType = typeof(A<>);
var a = aType.GetCustomAttributes(); // it uses the NullableContext from here
var aConstraint = aType.GetGenericArguments()[0].GetCustomAttributes(); // and here is where the type constraint nullability is stored
public class A<B> where B : class { } Add a few more members to force the context to be either nullable or non-nullable, and then experiment with the type constraint. Of course value types would be known to be a value type, but the proper nullability byte should be set for reference types. |
Anyway, maybe I'm missing something, and there's actually somewhere else it's being stored also. @buyaa-n can you review? |
Thanks for the examples @Shane32, unfortunately i did not found any attributes anywhere for distinguishing
Agree it could be a compiler bug, you can file an issue in roslyn repo to find out for sure, i don't see any info for related to it in their doc, it is unfortunate that we could not give better answer than |
FYI, when I went back to this I couldn't reproduce the issue; I'm not sure why. (It seemed pretty clear at the time.) Can anyone else reproduce the issue with that particular case? I have not checked recently. |
@buyaa-n isnt the base issue here around unrestricted generic parameters (the original bug report at the top of this thread) still unresolved? I think you maybe felt strongly that the current behavior should stay as is which is why I didn’t attempt to address it. Maybe this gets closed as by design? |
Strange, i could not repro either, now seems correct
Seems you were proposing to return
Yes i feel current behavior should stay, thanks for clarification @madelson, closing the issue as by design |
Description
For a generic parameter
T
on a class with no type constraints indicating nullability,Unknown
would seem most appropriate nullability state to return fromNullabilityInfoContext
because there are no constraints on that type with respect to nullability.However, the value returned seems to depend on whether the type is declared in a nullable or non-nullable context
Reproduction Steps
Expected behavior
Consistently returns Unknown.
Actual behavior
Sometimes returns NotNull.
Regression?
No response
Known Workarounds
No response
Configuration
VS 17.0.2, .NET 6, Windows 10 x64.
I don't have any reason to believe this is configuration specific.
Other information
C#9 allows for both
T
andT?
to be used in generic classes even without any constraint that indicates whether or notT
can be null:It's unclear what
NullabilityInfoContext
should return when reflecting over such types. On one hand, both could be either non-nullable or nullable depending on the particular generic argument that ends up getting used, which makesUnknown
seem reasonable for both from that perspective.On the other hand, it seems nice to be able to differentiate between the two in some way. Perhaps it makes sense to add another null state that explicitly represents the case of
NotNullIfGenericArgumentIsNonNullableReferenceType
?The text was updated successfully, but these errors were encountered: