From c15d953d716bf56e8a5a6f918bed61fa39ba73d1 Mon Sep 17 00:00:00 2001 From: Linus Hamlin Date: Mon, 13 Apr 2026 14:55:01 +0200 Subject: [PATCH 1/2] Fix nullability analysis of property indexers --- .../Reflection/NullabilityInfoContext.cs | 26 +++++++++++-- .../Reflection/NullabilityInfoContextTests.cs | 37 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs index b2956b14e1bf98..8d3f9d862e888a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -69,10 +69,30 @@ public NullabilityInfo Create(ParameterInfo parameterInfo) { ArgumentNullException.ThrowIfNull(parameterInfo); + bool annotationsDisabled = false; + if (parameterInfo.Member is PropertyInfo propertyInfo) + { + // For property indexers, switch to the respective getter/setter parameter. + if (propertyInfo.GetGetMethod(true) is { } getter && !IsPrivateOrInternalMethodAndAnnotationDisabled(getter)) + { + parameterInfo = getter.GetParametersAsSpan()[parameterInfo.Position]; + } + else if (propertyInfo.GetSetMethod(true) is { } setter && !IsPrivateOrInternalMethodAndAnnotationDisabled(setter)) + { + parameterInfo = setter.GetParametersAsSpan()[parameterInfo.Position]; + } + else + { + annotationsDisabled = true; + } + } + else if (parameterInfo.Member is MethodBase method) + { + annotationsDisabled = IsPrivateOrInternalMethodAndAnnotationDisabled(method); + } + IList attributes = parameterInfo.GetCustomAttributesData(); - NullableAttributeStateParser parser = parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method) - ? NullableAttributeStateParser.Unknown - : CreateParser(attributes); + NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(attributes); NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser); if (nullability.ReadState != NullabilityState.Unknown) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs index 25e80df1fa867e..6628c6b2e13308 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs @@ -1471,6 +1471,25 @@ public void TestMethodsWithGenericParameters(Delegate @delegate, NullabilityStat Assert.Equal(expectedRead, info.ReadState); Assert.Equal(expectedWrite, info.WriteState); } + + public static IEnumerable TestClassesWithIndexers() => new object[][] + { + [typeof(ClassWithOnlyIndexedGetProperty), "Item", NullabilityState.NotNull, NullabilityState.NotNull], + [typeof(ClassWithIndexedGetPropertyAndTupleProperty), "Item", NullabilityState.NotNull, NullabilityState.NotNull], + [typeof(ClassWithOnlyIndexedSetProperty), "Item", NullabilityState.NotNull, NullabilityState.NotNull], + [typeof(ClassWithIndexedSetPropertyAndTupleProperty), "Item", NullabilityState.NotNull, NullabilityState.NotNull], + }; + + [Theory] + [MemberData(nameof(TestClassesWithIndexers))] + public void TestPropertyIndexer(Type type, string propertyName, NullabilityState expectedRead, NullabilityState expectedWrite) + { + var ctx = new NullabilityInfoContext(); + PropertyInfo property = type.GetProperty(propertyName)!; + NullabilityInfo info = nullabilityContext.Create(property.GetIndexParameters()[0]); + Assert.Equal(expectedRead, info.ReadState); + Assert.Equal(expectedWrite, info.WriteState); + } } #pragma warning disable CS0649, CS0067, CS0414 @@ -1806,4 +1825,22 @@ public class ClassWithGenericMethods_Allow public static void GenericMethod([AllowNull] T value) => throw new Exception(); public static void GenericNotNullMethod([AllowNull] T value) where T : notnull => throw new Exception(); } + public class ClassWithOnlyIndexedGetProperty + { + public string this[string name] { get => throw new Exception(); } + } + public class ClassWithIndexedGetPropertyAndTupleProperty + { + public string this[string name] { get => throw new Exception(); } + public (int A, int B) ValueTupleProp { get; } + } + public class ClassWithOnlyIndexedSetProperty + { + public string this[string name] { set => throw new Exception(); } + } + public class ClassWithIndexedSetPropertyAndTupleProperty + { + public string this[string name] { set => throw new Exception(); } + public (int A, int B) ValueTupleProp { get; } + } } From 111edee7124c75b01e69e1f523cf89405afebd2a Mon Sep 17 00:00:00 2001 From: Linus Hamlin Date: Mon, 13 Apr 2026 16:52:11 +0200 Subject: [PATCH 2/2] FB --- .../System/Reflection/NullabilityInfoContextTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs index 6628c6b2e13308..d20d7b50831906 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/NullabilityInfoContextTests.cs @@ -1484,7 +1484,6 @@ public void TestMethodsWithGenericParameters(Delegate @delegate, NullabilityStat [MemberData(nameof(TestClassesWithIndexers))] public void TestPropertyIndexer(Type type, string propertyName, NullabilityState expectedRead, NullabilityState expectedWrite) { - var ctx = new NullabilityInfoContext(); PropertyInfo property = type.GetProperty(propertyName)!; NullabilityInfo info = nullabilityContext.Create(property.GetIndexParameters()[0]); Assert.Equal(expectedRead, info.ReadState);