Skip to content
Open
23 changes: 23 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/LookupResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,36 @@ internal void MergeEqual(SingleLookupResult result)
{
this.SetFrom(result);
}
else if (Kind == LookupResultKind.WrongArity && result.Kind == LookupResultKind.WrongArity)
{
// When both results are WrongArity, prefer the generic type over the non-generic one
// if the user supplied type arguments
if (PreferGenericOverNonGeneric(this.SingleSymbolOrDefault, result.Symbol))
{
this.SetFrom(result);
}
else if (!PreferGenericOverNonGeneric(result.Symbol, this.SingleSymbolOrDefault))
{
// Neither is preferred, add both symbols
if ((object)result.Symbol != null)
{
_symbolList.Add(result.Symbol);
}
}
// else: existing result is preferred (generic over non-generic), keep it
}
else if ((object)result.Symbol != null)
{
// Same goodness. Include all symbols
_symbolList.Add(result.Symbol);
}
}

private static bool PreferGenericOverNonGeneric(Symbol currentSymbol, Symbol newSymbol)
Copy link
Member

Choose a reason for hiding this comment

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

@copilot make this a static local function inside MergeEqual

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in commit 7b5a8c6.

{
return currentSymbol is NamedTypeSymbol { Arity: 0 } && newSymbol is NamedTypeSymbol { Arity: > 0 };
}

// global pool
//TODO: consider if global pool is ok.
private static readonly ObjectPool<LookupResult> s_poolInstance = CreatePool();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1833,9 +1833,9 @@ class C
// (20,27): error CS0305: Using the generic type 'N.A<T>' requires '1' type arguments
//
Diagnostic(ErrorCode.ERR_BadArity, "A").WithArguments("N.A<T>", "type", "1"),
// (21,34): error CS0308: The non-generic type 'N.B' cannot be used with type arguments
// (21,34): error CS0305: Using the generic type 'N.B<T1, T2>' requires '2' type arguments
//
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "B<int>").WithArguments("N.B", "type")
Diagnostic(ErrorCode.ERR_BadArity, "B<int>").WithArguments("N.B<T1, T2>", "type", "2")
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class ImprovedArityErrorTests : CSharpTestBase
{
[Fact]
Copy link
Member

Choose a reason for hiding this comment

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

add an appropriate WorkItem attribute to all these tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in commit a2c4d9f.

public void TestGenericAndNonGenericType()
{
var text = @"
Copy link
Member

Choose a reason for hiding this comment

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

@copilot make all these test strings into raw string literals.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in commit a2c4d9f.

class MyExpression { }
class MyExpression<T> { }
class Test
{
void M()
{
MyExpression<int, string> x;
}
}
";
// Currently reports: The non-generic type 'MyExpression' cannot be used with type arguments
// Should report: Using the generic type 'MyExpression<T>' requires 1 type arguments (not 2)
CreateCompilation(text).VerifyDiagnostics(
// (9,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string>").WithArguments("MyExpression<T>", "type", "1").WithLocation(9, 9),
// (9,35): warning CS0168: The variable 'x' is declared but never used
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(9, 35));
}

[Fact]
public void TestGenericAndNonGenericType_SingleTypeArgument()
{
var text = @"
class MyExpression { }
class MyExpression<T> { }
class Test
{
void M()
{
MyExpression<int> x = null;
}
}
";
// With the correct arity, should use the generic type successfully
CreateCompilation(text).VerifyDiagnostics(
// (9,35): warning CS0219: The variable 'x' is assigned but its value is never used
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x").WithArguments("x").WithLocation(9, 27));
}

[Fact]
public void TestNonGenericTypeOnly()
{
var text = @"
class MyExpression { }
class Test
{
void M()
{
MyExpression<int> x;
}
}
";
// When there's only a non-generic type, should still report ERR_HasNoTypeVars
CreateCompilation(text).VerifyDiagnostics(
// (8,9): error CS0308: The non-generic type 'MyExpression' cannot be used with type arguments
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "MyExpression<int>").WithArguments("MyExpression", "type").WithLocation(8, 9),
// (8,27): warning CS0168: The variable 'x' is declared but never used
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(8, 27));
}

[Fact]
public void TestGenericTypeOnly()
{
var text = @"
class MyExpression<T> { }
class Test
{
void M()
{
MyExpression<int, string> x;
}
}
";
// When there's only a generic type with wrong arity, should report ERR_BadArity
CreateCompilation(text).VerifyDiagnostics(
// (8,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string>").WithArguments("MyExpression<T>", "type", "1").WithLocation(8, 9),
// (8,35): warning CS0168: The variable 'x' is declared but never used
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(8, 35));
}

[Fact]
public void TestMultipleGenericTypes()
{
var text = @"
class MyExpression { }
class MyExpression<T> { }
class MyExpression<T1, T2> { }
class Test
{
void M()
{
MyExpression<int, string, bool> x;
}
}
";
// With multiple generic types and non-generic, should prefer the closest match
// In this case, we look for arity 3, find generic with arity 2, that's better than non-generic
CreateCompilation(text).VerifyDiagnostics(
// (10,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string, bool>").WithArguments("MyExpression<T>", "type", "1").WithLocation(10, 9),
// (10,41): warning CS0168: The variable 'x' is declared but never used
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(10, 41));
}
}
}
Loading