Skip to content

Commit f695099

Browse files
CopilotCyrusNajmabadijcouv
authored
Improve arity error message to prefer generic type when both generic and non-generic exist (#80707)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> Co-authored-by: Cyrus Najmabadi <cyrus.najmabadi@gmail.com> Co-authored-by: jcouv <12466233+jcouv@users.noreply.github.com>
1 parent 8a96674 commit f695099

File tree

4 files changed

+230
-12
lines changed

4 files changed

+230
-12
lines changed

src/Compilers/CSharp/Portable/Binder/LookupResult.cs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,17 +280,42 @@ internal void MergeEqual(SingleLookupResult result)
280280
{
281281
if (Kind > result.Kind)
282282
{
283-
// existing result is better
283+
// Existing result is strictly better. Ignore what is incoming.
284+
return;
284285
}
285-
else if (result.Kind > Kind)
286+
287+
if (result.Kind > Kind)
286288
{
289+
// Incoming result is better. Let it win completely over anything we've built up so far.
287290
this.SetFrom(result);
291+
return;
288292
}
289-
else if ((object)result.Symbol != null)
293+
294+
if (Kind == LookupResultKind.WrongArity && result.Kind == LookupResultKind.WrongArity)
290295
{
291-
// Same goodness. Include all symbols
292-
_symbolList.Add(result.Symbol);
296+
if (isNonGenericVersusGeneric(result.Symbol, this.SingleSymbolOrDefault))
297+
{
298+
// Current result is generic, and incoming is not. We just want stick with what we currently have
299+
// as the better symbol to be referring to when generics are provided, but arity is wrong.
300+
return;
301+
}
302+
303+
if (isNonGenericVersusGeneric(this.SingleSymbolOrDefault, result.Symbol))
304+
{
305+
// Current result is non generic, but incoming is generic. It's strictly the better symbol to be
306+
// referring to when generics are provided, but arity is wrong.
307+
this.SetFrom(result);
308+
return;
309+
}
310+
311+
// Neither is preferred, fall through and include all symbols.
293312
}
313+
314+
// Same goodness. Include all symbols
315+
_symbolList.AddIfNotNull(result.Symbol);
316+
317+
static bool isNonGenericVersusGeneric(Symbol firstSymbol, Symbol secondSymbol)
318+
=> firstSymbol.GetArity() == 0 && secondSymbol.GetArity() > 0;
294319
}
295320

296321
// global pool

src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,9 +1833,9 @@ class C
18331833
// (20,27): error CS0305: Using the generic type 'N.A<T>' requires '1' type arguments
18341834
//
18351835
Diagnostic(ErrorCode.ERR_BadArity, "A").WithArguments("N.A<T>", "type", "1"),
1836-
// (21,34): error CS0308: The non-generic type 'N.B' cannot be used with type arguments
1836+
// (21,34): error CS0305: Using the generic type 'N.B<T1, T2>' requires '2' type arguments
18371837
//
1838-
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "B<int>").WithArguments("N.B", "type")
1838+
Diagnostic(ErrorCode.ERR_BadArity, "B<int>").WithArguments("N.B<T1, T2>", "type", "2")
18391839
);
18401840
}
18411841

@@ -4080,5 +4080,198 @@ static void M(bool a, object b)
40804080

40814081
VerifyOperationTree(comp, model.GetOperation(ifStmt), operationString);
40824082
}
4083+
4084+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4085+
public void TestGenericAndNonGenericType()
4086+
{
4087+
var text = """
4088+
class MyExpression { }
4089+
class MyExpression<T> { }
4090+
4091+
class Test
4092+
{
4093+
void M()
4094+
{
4095+
MyExpression<int, string> x;
4096+
}
4097+
}
4098+
""";
4099+
4100+
CreateCompilation(text).VerifyDiagnostics(
4101+
// (8,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
4102+
// MyExpression<int, string> x;
4103+
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string>").WithArguments("MyExpression<T>", "type", "1").WithLocation(8, 9),
4104+
// (8,35): warning CS0168: The variable 'x' is declared but never used
4105+
// MyExpression<int, string> x;
4106+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(8, 35));
4107+
}
4108+
4109+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4110+
public void TestGenericAndNonGenericType_SingleTypeArgument()
4111+
{
4112+
var text = """
4113+
class MyExpression { }
4114+
class MyExpression<T> { }
4115+
4116+
class Test
4117+
{
4118+
void M()
4119+
{
4120+
MyExpression<int> x = null;
4121+
}
4122+
}
4123+
""";
4124+
4125+
CreateCompilation(text).VerifyDiagnostics(
4126+
// (8,27): warning CS0219: The variable 'x' is assigned but its value is never used
4127+
// MyExpression<int> x = null;
4128+
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x").WithArguments("x").WithLocation(8, 27));
4129+
}
4130+
4131+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4132+
public void TestNonGenericTypeOnly()
4133+
{
4134+
var text = """
4135+
class MyExpression { }
4136+
4137+
class Test
4138+
{
4139+
void M()
4140+
{
4141+
MyExpression<int> x;
4142+
}
4143+
}
4144+
""";
4145+
4146+
CreateCompilation(text).VerifyDiagnostics(
4147+
// (7,9): error CS0308: The non-generic type 'MyExpression' cannot be used with type arguments
4148+
// MyExpression<int> x;
4149+
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "MyExpression<int>").WithArguments("MyExpression", "type").WithLocation(7, 9),
4150+
// (7,27): warning CS0168: The variable 'x' is declared but never used
4151+
// MyExpression<int> x;
4152+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(7, 27));
4153+
}
4154+
4155+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4156+
public void TestGenericTypeOnly()
4157+
{
4158+
var text = """
4159+
class MyExpression<T> { }
4160+
4161+
class Test
4162+
{
4163+
void M()
4164+
{
4165+
MyExpression<int, string> x;
4166+
}
4167+
}
4168+
""";
4169+
4170+
CreateCompilation(text).VerifyDiagnostics(
4171+
// (7,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
4172+
// MyExpression<int, string> x;
4173+
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string>").WithArguments("MyExpression<T>", "type", "1").WithLocation(7, 9),
4174+
// (7,35): warning CS0168: The variable 'x' is declared but never used
4175+
// MyExpression<int, string> x;
4176+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(7, 35));
4177+
}
4178+
4179+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4180+
public void TestMultipleGenericTypes()
4181+
{
4182+
var text = """
4183+
class MyExpression { }
4184+
class MyExpression<T> { }
4185+
class MyExpression<T1, T2> { }
4186+
4187+
class Test
4188+
{
4189+
void M()
4190+
{
4191+
MyExpression<int, string, bool> x;
4192+
}
4193+
}
4194+
""";
4195+
4196+
CreateCompilation(text).VerifyDiagnostics(
4197+
// (9,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
4198+
// MyExpression<int, string, bool> x;
4199+
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression<int, string, bool>").WithArguments("MyExpression<T>", "type", "1").WithLocation(9, 9),
4200+
// (9,41): warning CS0168: The variable 'x' is declared but never used
4201+
// MyExpression<int, string, bool> x;
4202+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(9, 41));
4203+
}
4204+
4205+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4206+
public void TestGenericTypeWithNoTypeArguments()
4207+
{
4208+
var text = """
4209+
class MyExpression { }
4210+
class MyExpression<T> { }
4211+
4212+
class Test
4213+
{
4214+
void M()
4215+
{
4216+
MyExpression x;
4217+
}
4218+
}
4219+
""";
4220+
4221+
// When both generic and non-generic versions exist and no type arguments are provided,
4222+
// the non-generic version is successfully used (no error expected)
4223+
CreateCompilation(text).VerifyDiagnostics(
4224+
// (8,22): warning CS0168: The variable 'x' is declared but never used
4225+
// MyExpression x;
4226+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(8, 22));
4227+
}
4228+
4229+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4230+
public void TestGenericTypeOnlyWithNoTypeArguments()
4231+
{
4232+
var text = """
4233+
class MyExpression<T> { }
4234+
4235+
class Test
4236+
{
4237+
void M()
4238+
{
4239+
MyExpression x;
4240+
}
4241+
}
4242+
""";
4243+
4244+
// When only a generic type exists and no type arguments are provided,
4245+
// an error is reported about the required type arguments
4246+
CreateCompilation(text).VerifyDiagnostics(
4247+
// (7,9): error CS0305: Using the generic type 'MyExpression<T>' requires 1 type arguments
4248+
// MyExpression x;
4249+
Diagnostic(ErrorCode.ERR_BadArity, "MyExpression").WithArguments("MyExpression<T>", "type", "1").WithLocation(7, 9),
4250+
// (7,22): warning CS0168: The variable 'x' is declared but never used
4251+
// MyExpression x;
4252+
Diagnostic(ErrorCode.WRN_UnreferencedVar, "x").WithArguments("x").WithLocation(7, 22));
4253+
}
4254+
4255+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24406")]
4256+
public void TestGenericAndNonGenericMethod()
4257+
{
4258+
var text = """
4259+
class Program
4260+
{
4261+
static void Main()
4262+
{
4263+
M<int>(default!);
4264+
}
4265+
4266+
void M(object obj) { }
4267+
T2 M<T1, T2>(T1 t) => throw null!;
4268+
}
4269+
""";
4270+
4271+
CreateCompilation(text).VerifyEmitDiagnostics(
4272+
// (5,9): error CS0305: Using the generic method 'Program.M<T1, T2>(T1)' requires 2 type arguments
4273+
// M<int>(default!);
4274+
Diagnostic(ErrorCode.ERR_BadArity, "M<int>").WithArguments("Program.M<T1, T2>(T1)", "method", "2").WithLocation(5, 9));
4275+
}
40834276
}
40844277
}

src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3588,12 +3588,12 @@ static void Main()
35883588
}";
35893589
var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);
35903590
comp.VerifyDiagnostics(
3591-
// (12,13): error CS0308: The non-generic method 'Program.F0(object)' cannot be used with type arguments
3591+
// (12,13): error CS0305: Using the generic method 'Program.F0<T>(object)' requires 1 type arguments
35923592
// d = F0<int, object>;
3593-
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "F0<int, object>").WithArguments("Program.F0(object)", "method").WithLocation(12, 13),
3594-
// (13,13): error CS0308: The non-generic method 'Program.F1(object)' cannot be used with type arguments
3593+
Diagnostic(ErrorCode.ERR_BadArity, "F0<int, object>").WithArguments("Program.F0<T>(object)", "method", "1").WithLocation(12, 13),
3594+
// (13,13): error CS0305: Using the generic method 'Program.F1<T, U>(object)' requires 2 type arguments
35953595
// d = F1<int>;
3596-
Diagnostic(ErrorCode.ERR_HasNoTypeVars, "F1<int>").WithArguments("Program.F1(object)", "method").WithLocation(13, 13),
3596+
Diagnostic(ErrorCode.ERR_BadArity, "F1<int>").WithArguments("Program.F1<T, U>(object)", "method", "2").WithLocation(13, 13),
35973597
// (14,13): error CS8917: The delegate type could not be inferred.
35983598
// d = F2;
35993599
Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F2").WithLocation(14, 13));

src/EditorFeatures/Test2/FindReferences/FindReferencesTests.NamedTypeSymbols.vb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ class A
446446
private C<T> c2;
447447
private C<int> c3;
448448
private C<C<T>> c4;
449-
private [|C|]<int,string> c5;
449+
private C<int,string> c5;
450450
}
451451
class C<T>
452452
{

0 commit comments

Comments
 (0)