Skip to content

Commit 0887419

Browse files
Copilotsbomerjtschuster
authored
Fix IL2067 warning reporting wrong parameter with named arguments in record constructors (#121637)
## Fix IL2067 Warning for Named Arguments in Record Primary Constructors ✅ **Issue**: Fixes #121629 - IL2067 warning incorrectly reports the wrong parameter when using named arguments in record primary constructor base calls. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> Co-authored-by: Jackson Schuster <36744439+jtschuster@users.noreply.github.com>
1 parent fed2757 commit 0887419

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,10 @@ private TValue ProcessMethodCall(
934934
instanceValue = TopValue;
935935
}
936936

937+
// Build a map from parameter to argument so we can handle named/out-of-order arguments correctly
938+
var parameters = method.Parameters;
939+
var argumentsByParameter = new TValue?[parameters.Length];
940+
937941
foreach (var argument in arguments)
938942
{
939943
// For __arglist argument there might not be any parameter
@@ -943,7 +947,19 @@ private TValue ProcessMethodCall(
943947
if (argument?.Parameter == null)
944948
break;
945949

946-
argumentsBuilder.Add(VisitArgument(argument, state));
950+
var parameter = argument.Parameter;
951+
// Use Ordinal to get the parameter's position
952+
int parameterIndex = parameter.Ordinal;
953+
if (parameterIndex >= 0 && parameterIndex < parameters.Length)
954+
{
955+
argumentsByParameter[parameterIndex] = VisitArgument(argument, state);
956+
}
957+
}
958+
959+
// Add arguments in parameter order, using TopValue for missing arguments (e.g., optional parameters)
960+
for (int i = 0; i < parameters.Length; i++)
961+
{
962+
argumentsBuilder.Add(argumentsByParameter[i] ?? TopValue);
947963
}
948964

949965
// For local functions with generic arguments, the substituted method symbol's containing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace ILLink.RoslynAnalyzer.Tests.Reflection
6+
{
7+
public sealed partial class DependenciesTests : LinkerTestBase
8+
{
9+
10+
protected override string TestSuiteName => "Reflection.Dependencies";
11+
12+
[Fact]
13+
public Task TypeMapReferencedAssembly()
14+
{
15+
return RunTest(allowMissingWarnings: true);
16+
}
17+
18+
}
19+
}

src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static void Main()
4949
AnnotationOnByRefParameter.Test();
5050
WriteCapturedParameter.Test();
5151
OperatorParameters.Test();
52+
NamedArgumentsWithAnnotations.Test();
5253
}
5354

5455
// Validate the error message when annotated parameter is passed to another annotated parameter
@@ -512,5 +513,52 @@ private static Type GetUnknownType()
512513
{
513514
return null;
514515
}
516+
517+
// https://github.com/dotnet/runtime/issues/121629
518+
class NamedArgumentsWithAnnotations
519+
{
520+
record BaseRecord(
521+
string? OptionalParameter = null,
522+
[property: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
523+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
524+
Type? AnnotatedParameter = null,
525+
object? UnannotatedParameter = null
526+
);
527+
528+
// This should not warn about 'UnannotatedParameter'
529+
record DerivedRecord(
530+
[param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicConstructors)]
531+
Type? AnnotatedParameter = default,
532+
object? UnannotatedParameter = default
533+
) : BaseRecord(
534+
AnnotatedParameter: AnnotatedParameter,
535+
UnannotatedParameter: UnannotatedParameter
536+
);
537+
538+
static void RequiresPublicConstructors(
539+
string? optionalParameter = null,
540+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
541+
Type? annotatedParameter = null,
542+
object? unannotatedParameter = null)
543+
{
544+
}
545+
546+
// This should not warn about 'unannotatedParameter'
547+
static void CallWithNamedArguments(
548+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicConstructors)]
549+
Type? annotatedParameter,
550+
object? unannotatedParameter)
551+
{
552+
RequiresPublicConstructors(
553+
annotatedParameter: annotatedParameter,
554+
unannotatedParameter: unannotatedParameter);
555+
}
556+
557+
public static void Test()
558+
{
559+
var x = new DerivedRecord(typeof(string), "data");
560+
CallWithNamedArguments(typeof(string), "data");
561+
}
562+
}
515563
}
516564
}

0 commit comments

Comments
 (0)