Skip to content

Commit

Permalink
Fix #72457
Browse files Browse the repository at this point in the history
  • Loading branch information
Rekkonnect committed Apr 2, 2024
1 parent 399051e commit 82fef22
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12696,6 +12696,179 @@ public enum Enum { A, B, C, D }
""";
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")]
public async Task ConstrainedGenericExtensionMethods_01()
{
var markup = """
using System.Collections.Generic;
using System.Linq;
namespace Extensions;
public static class GenericExtensions
{
public static string FirstOrDefaultOnHashSet<T>(this T s)
where T : HashSet<string>
{
return s.FirstOrDefault();
}
public static string FirstOrDefaultOnList<T>(this T s)
where T : List<string>
{
return s.FirstOrDefault();
}
}
class C
{
void M()
{
var list = new List<string>();
list.$$
}
}
""";

await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>");
await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")]
public async Task ConstrainedGenericExtensionMethods_02()
{
var markup = """
using System.Collections.Generic;
using System.Linq;
namespace Extensions;
public static class GenericExtensions
{
public static string FirstOrDefaultOnHashSet<T>(this T s)
where T : HashSet<string>
{
return s.FirstOrDefault();
}
public static string FirstOrDefaultOnList<T>(this T s)
where T : List<string>
{
return s.FirstOrDefault();
}
public static bool HasFirstNonNullItemOnList<T>(this T s)
where T : List<string>
{
return s.$$
}
}
""";

await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>");
await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")]
public async Task ConstrainedGenericExtensionMethods_SelfGeneric01()
{
var markup = """
using System.Collections.Generic;
using System.Linq;
namespace Extensions;
public static class GenericExtensions
{
public static T FirstOrDefaultOnHashSet<T>(this T s)
where T : HashSet<T>
{
return s.FirstOrDefault();
}
public static T FirstOrDefaultOnList<T>(this T s)
where T : List<T>
{
return s.FirstOrDefault();
}
}
public class ListExtension<T> : List<ListExtension<T>>
where T : List<T>
{
public void Method()
{
this.$$
}
}
""";

await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>");
await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")]
public async Task ConstrainedGenericExtensionMethods_SelfGeneric02()
{
var markup = """
using System.Collections.Generic;
using System.Linq;
namespace Extensions;
public static class GenericExtensions
{
public static T FirstOrDefaultOnHashSet<T>(this T s)
where T : HashSet<T>
{
return s.FirstOrDefault();
}
public static T FirstOrDefaultOnList<T>(this T s)
where T : List<T>
{
return s.FirstOrDefault();
}
public static bool HasFirstNonNullItemOnList<T>(this T s)
where T : List<T>
{
return s.$$
}
}
""";

await VerifyItemExistsAsync(markup, "FirstOrDefaultOnList", displayTextSuffix: "<>");
await VerifyItemIsAbsentAsync(markup, "FirstOrDefaultOnHashSet", displayTextSuffix: "<>");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72457")]
public async Task ConstrainedGenericExtensionMethods_SelfGeneric03()
{
var markup = """
namespace Extensions;
public interface IBinaryInteger<T>
{
public static T AdditiveIdentity { get; }
}
public static class GenericExtensions
{
public static T AtLeastAdditiveIdentity<T>(this T s)
where T : IBinaryInteger<T>
{
return T.AdditiveIdentity > s ? s : T.AdditiveIdentity;
}
public static T Method<T>(this T s)
where T : IBinaryInteger<T>
{
return s.$$
}
}
""";

await VerifyItemExistsAsync(markup, "AtLeastAdditiveIdentity", displayTextSuffix: "<>");
await VerifyItemExistsAsync(markup, "Method", displayTextSuffix: "<>");
}

#region Collection expressions

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,21 +455,45 @@ static bool MatchesConstraint(ITypeSymbol originalContainerType, ITypeSymbol ori
}
else if (originalConstraintType.TypeKind == TypeKind.Interface)
{
// If the constraint is an interface then see if that interface appears in the interface inheritance
// hierarchy of the type we're dotting off of.
foreach (var interfaceType in originalContainerType.AllInterfaces)
if (originalContainerType is ITypeParameterSymbol typeParameterContainer)
{
if (SymbolEqualityComparer.Default.Equals(interfaceType.OriginalDefinition, originalConstraintType))
return true;
// If the container type is a type parameter, we attempt to match all the interfaces from its constraint types.
foreach (var constraintType in typeParameterContainer.ConstraintTypes)
{
foreach (var constraintTypeInterface in constraintType.GetAllInterfacesIncludingThis())
{
if (SymbolEqualityComparer.Default.Equals(constraintTypeInterface.OriginalDefinition, originalConstraintType))
return true;
}
}
}
else
{
// If the constraint is an interface then see if that interface appears in the interface inheritance
// hierarchy of the type we're dotting off of.
foreach (var interfaceType in originalContainerType.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(interfaceType.OriginalDefinition, originalConstraintType))
return true;
}
}
}
else if (originalConstraintType.TypeKind == TypeKind.Class)
{
// If the constraint is an interface then see if that interface appears in the base type inheritance
// hierarchy of the type we're dotting off of.
for (var current = originalContainerType.BaseType; current != null; current = current.BaseType)
if (originalContainerType is ITypeParameterSymbol typeParameterContainer)
{
if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, originalConstraintType))
// If the container type is a type parameter, we iterate through all the type's constrained types.
foreach (var constrainedType in typeParameterContainer.ConstraintTypes)
{
if (MatchesAnyBaseTypes(constrainedType, originalConstraintType))
return true;
}
}
else
{
// If the constraint is an interface then see if that interface appears in the base type inheritance
// hierarchy of the type we're dotting off of.
if (MatchesAnyBaseTypes(originalContainerType.BaseType, originalConstraintType))
return true;
}
}
Expand All @@ -483,6 +507,17 @@ static bool MatchesConstraint(ITypeSymbol originalContainerType, ITypeSymbol ori

// For anything else, we don't consider this a match. This can be adjusted in the future if need be.
return false;

static bool MatchesAnyBaseTypes(ITypeSymbol source, ITypeSymbol matched)
{
for (var current = source; current != null; current = current.BaseType)
{
if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, matched))
return true;
}

return false;
}
}
}

Expand Down

0 comments on commit 82fef22

Please sign in to comment.