Skip to content

Commit

Permalink
Merge pull request #72853 from Rekkonnect/fix/72457
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Apr 5, 2024
2 parents 5019bd5 + 82fef22 commit 102794e
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 102794e

Please sign in to comment.