Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// #define LOG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this related to the PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's removing dead code in the IDE in this feature. We don't use this pattern.


using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -17,41 +15,30 @@
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

#if LOG
using System.IO;
using System.Text.RegularExpressions;
#endif

namespace Microsoft.CodeAnalysis.SimplifyTypeNames;

internal abstract class SimplifyTypeNamesDiagnosticAnalyzerBase<TLanguageKindEnum, TSimplifierOptions>
: AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer
where TLanguageKindEnum : struct
where TSimplifierOptions : SimplifierOptions
{
#if LOG
private static string _logFile = @"c:\temp\simplifytypenames.txt";
private static object _logGate = new object();
private static readonly Regex s_newlinePattern = new Regex(@"[\r\n]+");
#endif

private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(AnalyzersResources.Name_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));

private static readonly LocalizableString s_localizableTitleSimplifyNames = new LocalizableResourceString(nameof(AnalyzersResources.Simplify_Names), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
private static readonly DiagnosticDescriptor s_descriptorSimplifyNames = CreateDescriptorWithId(IDEDiagnosticIds.SimplifyNamesDiagnosticId,
EnforceOnBuildValues.SimplifyNames,
hasAnyCodeStyleOption: false,
s_localizableTitleSimplifyNames,
s_localizableMessage,
isUnnecessary: true);
EnforceOnBuildValues.SimplifyNames,
hasAnyCodeStyleOption: false,
s_localizableTitleSimplifyNames,
s_localizableMessage,
isUnnecessary: true);

private static readonly LocalizableString s_localizableTitleSimplifyMemberAccess = new LocalizableResourceString(nameof(AnalyzersResources.Simplify_Member_Access), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
private static readonly DiagnosticDescriptor s_descriptorSimplifyMemberAccess = CreateDescriptorWithId(IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId,
EnforceOnBuildValues.SimplifyMemberAccess,
hasAnyCodeStyleOption: false,
s_localizableTitleSimplifyMemberAccess,
s_localizableMessage,
isUnnecessary: true);
EnforceOnBuildValues.SimplifyMemberAccess,
hasAnyCodeStyleOption: false,
s_localizableTitleSimplifyMemberAccess,
s_localizableMessage,
isUnnecessary: true);

private static readonly DiagnosticDescriptor s_descriptorPreferBuiltinOrFrameworkType = CreateDescriptorWithId(IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId,
EnforceOnBuildValues.PreferBuiltInOrFrameworkType,
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ public virtual TypeSymbol ReceiverType
/// <exception cref="System.InvalidOperationException">If this is not a reduced extension method.</exception>
/// <exception cref="System.ArgumentNullException">If <paramref name="reducedFromTypeParameter"/> is null.</exception>
/// <exception cref="System.ArgumentException">If <paramref name="reducedFromTypeParameter"/> doesn't belong to the corresponding <see cref="ReducedFrom"/> method.</exception>
public virtual TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
public virtual TypeWithAnnotations GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
{
throw new InvalidOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public override TypeSymbol ReceiverType
internal override CodeAnalysis.NullableAnnotation ReceiverNullableAnnotation =>
_reducedFrom.Parameters[0].TypeWithAnnotations.ToPublicAnnotation();

public override TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
public override TypeWithAnnotations GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
{
if ((object)reducedFromTypeParameter == null)
{
Expand All @@ -266,7 +266,7 @@ public override TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol re
throw new System.ArgumentException();
}

return null;
return default;
}

public override MethodSymbol ReducedFrom
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,13 @@ public override TypeSymbol ReceiverType
}
}

public override TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
public override TypeWithAnnotations GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
{
// This will throw if API shouldn't be supported or there is a problem with the argument.
var notUsed = OriginalDefinition.GetTypeInferredDuringReduction(reducedFromTypeParameter);

Debug.Assert((object)notUsed == null && (object)OriginalDefinition.ReducedFrom != null);
return this.TypeArgumentsWithAnnotations[reducedFromTypeParameter.Ordinal].Type;
Debug.Assert(notUsed.Type is null && OriginalDefinition.ReducedFrom is not null);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: GetTypeInferredDuringReduction calls into itself on the OriginalDef prop. So 'notUsed' now became a TypeWithAnnotations as well, requiring this check to change.

return this.TypeArgumentsWithAnnotations[reducedFromTypeParameter.Ordinal];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the crux of the fix. by returning 'Type' we were losing the ? on C2?, leading to the answer of "what what Select<C1, C2?> constructed from?" being "Enumerable.Select<C1, C2>" not "Enumerable.Select<C1, C2?>".

This then broke an ide scenario because it looked like the compiler was saying all the type arguments before/after a change we were making were identical, when really it was losing info.

}

public sealed override MethodSymbol ReducedFrom
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6048,5 +6048,62 @@ class B<T, U, U>
symbol = model.GetDeclaredSymbol(typeParameters[typeParameters.Length - 2]);
Assert.True(symbol.IsReferenceType);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75570")]
public void GetConstructedReducedFrom_WithNullableInferredType()
{
var source = """
#nullable enable

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

class C1 { }

class C2 { }

class D
{
public static void ConvertAll(IEnumerable<C1> values)
{
var v = values.Select(Convert);
}

[return: NotNullIfNotNull(nameof(value))]
private static C2? Convert(C1? value) => value is null ? null : new C2();
}
""" + NotNullIfNotNullAttributeDefinition;

var comp = CreateCompilationWithMscorlib40AndSystemCore(source).VerifyDiagnostics();

var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var memberAccess = tree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>().First();
var constructedMethodSymbol = (IMethodSymbol)model.GetSymbolInfo(memberAccess).Symbol;

// Ensure that both the bound reduced method symbol, and the constructed method symbol
// it was reduced from both have the right nullable annotations for the type parameters.
Assert.True(constructedMethodSymbol is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a bit simpler and easier to evaluate a difference, if a ToTestDisplayString including the nullability were used. However, no need to make a change, if you prefer this way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally like hitting the symbols directly :)

{
Name: nameof(Enumerable.Select),
TypeArguments: [
{ Name: "C1", NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.NotAnnotated },
{ Name: "C2", NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.Annotated }
],
ReceiverType.Name: nameof(IEnumerable<>),
});

var constructedReducedFrom = constructedMethodSymbol.GetConstructedReducedFrom();
Assert.True(constructedReducedFrom is
{
Name: nameof(Enumerable.Select),
TypeArguments: [
{ Name: "C1", NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.NotAnnotated },
{ Name: "C2", NullableAnnotation: Microsoft.CodeAnalysis.NullableAnnotation.Annotated }
],
ReceiverType.Name: nameof(Enumerable),
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6832,6 +6832,59 @@ void M()
}
""");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75570")]
public Task GenericMethodArgsWithNullabilityDifference1()
=> TestMissingInRegularAndScriptAsync(
"""
#nullable enable

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

class C1 { }

class C2 { }

class D
{
public static C2[] ConvertAll(IEnumerable<C1> values)
{
return values.[|Select<C1, C2>|](Convert).ToArray();
}

[return: NotNullIfNotNull(nameof(value))]
private static C2? Convert(C1? value) => value is null ? null : new C2();
}
""");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/45484")]
public Task GenericMethodArgsWithNullabilityDifference2()
=> TestMissingInRegularAndScriptAsync(
"""
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

class C
{
bool M(IEnumerable<string> s, [NotNullWhen(true)] out string? result)
{
return s.[|TryFirst<string>|](x => x.Length % 2 == 0, out result);
}
}

static class Extensions
{
public static bool TryFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate, [MaybeNullWhen(false)] out T value)
{
throw new NotImplementedException();
}
}
""");

private async Task TestWithPredefinedTypeOptionsAsync(string code, string expected, int index = 0)
=> await TestInRegularAndScript1Async(code, expected, index, new TestParameters(options: PreferIntrinsicTypeEverywhere));

Expand Down
Loading