-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix serialization of attribute data to account for named params argument #22231
Changes from 1 commit
d19eca9
c5941a9
3612926
6163640
af667ae
cefce71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -825,49 +825,39 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |
{ | ||
Debug.Assert(argsConsumedCount <= argumentsCount); | ||
|
||
int paramArrayArgCount = argumentsCount - argsConsumedCount; | ||
if (paramArrayArgCount == 0) | ||
{ | ||
return new TypedConstant(parameter.Type, ImmutableArray<TypedConstant>.Empty); | ||
} | ||
|
||
int argIndex = -1; | ||
// If there's a named argument, we'll use that | ||
if (!constructorArgumentNamesOpt.IsDefault) | ||
{ | ||
argIndex = constructorArgumentNamesOpt.IndexOf(parameter.Name); | ||
int argIndex = constructorArgumentNamesOpt.IndexOf(parameter.Name); | ||
if (TryGetNormalParamValue(parameter, constructorArgsArray, argIndex, conversions, out var namedValue)) | ||
{ | ||
return namedValue; | ||
} | ||
|
||
// A named argument for a params parameter must be trailing, so expanded params must have a single value | ||
var singleValue = new TypedConstant[1] { constructorArgsArray[argIndex] }; | ||
return new TypedConstant(parameter.Type, singleValue.AsImmutableOrNull()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid the extra array with |
||
} | ||
|
||
// If there's exactly one argument and it's an array, we'll use that | ||
if (argIndex < 0 && paramArrayArgCount == 1 && constructorArgsArray[argsConsumedCount].Kind == TypedConstantKind.Array) | ||
int paramArrayArgCount = argumentsCount - argsConsumedCount; | ||
|
||
// If there are zero arguments left | ||
if (paramArrayArgCount == 0) | ||
{ | ||
argIndex = argsConsumedCount; | ||
return new TypedConstant(parameter.Type, ImmutableArray<TypedConstant>.Empty); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor point: consider handling the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I think it must be after the named arguments case. In reply to: 140549685 [](ancestors = 140549685) |
||
|
||
if (argIndex >= 0) | ||
// If there's exactly one argument left and it's an array, we'll try that | ||
if (paramArrayArgCount == 1 && | ||
TryGetNormalParamValue(parameter, constructorArgsArray,argsConsumedCount, conversions, out var lastValue)) | ||
{ | ||
TypeSymbol argumentType = (TypeSymbol)constructorArgsArray[argIndex].Type; | ||
|
||
// Easy out (i.e. don't both classifying conversion). | ||
if (argumentType == parameter.Type) | ||
{ | ||
return constructorArgsArray[argIndex]; | ||
} | ||
|
||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; // ignoring, since already bound argument and parameter | ||
Conversion conversion = conversions.ClassifyBuiltInConversion(argumentType, parameter.Type, ref useSiteDiagnostics); | ||
|
||
// NOTE: Won't always succeed, even though we've performed overload resolution. | ||
// For example, passing int[] to params object[] actually treats the int[] as an element of the object[]. | ||
if (conversion.IsValid && conversion.Kind == ConversionKind.ImplicitReference) | ||
{ | ||
return constructorArgsArray[argIndex]; | ||
} | ||
return lastValue; | ||
} | ||
|
||
Debug.Assert(!constructorArgsArray.IsDefault); | ||
Debug.Assert(argsConsumedCount <= constructorArgsArray.Length); | ||
|
||
// Take the trailing arguments as an array for expanded form | ||
var values = new TypedConstant[paramArrayArgCount]; | ||
|
||
for (int i = 0; i < paramArrayArgCount; i++) | ||
|
@@ -878,6 +868,39 @@ private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, Attribu | |
return new TypedConstant(parameter.Type, values.AsImmutableOrNull()); | ||
} | ||
|
||
private static bool TryGetNormalParamValue(ParameterSymbol parameter, ImmutableArray<TypedConstant> constructorArgsArray, | ||
int argIndex, Conversions conversions, out TypedConstant result) | ||
{ | ||
TypedConstant argument = constructorArgsArray[argIndex]; | ||
if (argument.Kind != TypedConstantKind.Array) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we hit this case with |
||
result = default; | ||
return false; | ||
} | ||
|
||
TypeSymbol argumentType = (TypeSymbol)argument.Type; | ||
// Easy out (i.e. don't both classifying conversion). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (argumentType == parameter.Type) | ||
{ | ||
result = argument; | ||
return true; | ||
} | ||
|
||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; // ignoring, since already bound argument and parameter | ||
Conversion conversion = conversions.ClassifyBuiltInConversion(argumentType, parameter.Type, ref useSiteDiagnostics); | ||
|
||
// NOTE: Won't always succeed, even though we've performed overload resolution. | ||
// For example, passing int[] to params object[] actually treats the int[] as an element of the object[]. | ||
if (conversion.IsValid && conversion.Kind == ConversionKind.ImplicitReference) | ||
{ | ||
result = argument; | ||
return true; | ||
} | ||
|
||
result = default; | ||
return false; | ||
} | ||
|
||
#endregion | ||
|
||
#region AttributeExpressionVisitor | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,39 +99,86 @@ sealed class MarkAttribute : Attribute | |
{ | ||
public MarkAttribute(bool a, params object[] b) | ||
{ | ||
A = a; | ||
B = b; | ||
} | ||
public bool A { get; } | ||
public object[] B { get; } | ||
public object[] B { get; } | ||
} | ||
|
||
[Mark(b: new object[] { ""Hello"", ""World"" }, a: true)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider testing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. That is broken. I'm investigating. #Resolved |
||
static class Program | ||
{ | ||
private static void Test(bool a, params object[] b) | ||
=> PrintOutArgsInfo(b); | ||
|
||
public static void Main() | ||
{ | ||
Console.Write(""Method call: ""); | ||
Test(b: new object[] { ""Hello"", ""World"" }, a: true); | ||
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>(); | ||
Console.Write(attr.B[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider verifying the length of the array and the value of |
||
} | ||
}", options: TestOptions.DebugExe); | ||
source.VerifyDiagnostics(); | ||
|
||
CompileAndVerify(source, expectedOutput: @"Hello"); | ||
} | ||
|
||
[Fact] | ||
[WorkItem(20741, "https://github.com/dotnet/roslyn/issues/20741")] | ||
public void TestNamedArgumentOnObjectParamsArgument2() | ||
{ | ||
var source = CreateCompilationWithMscorlib46(@" | ||
using System; | ||
using System.Reflection; | ||
|
||
sealed class MarkAttribute : Attribute | ||
{ | ||
public MarkAttribute(bool a, params object[] b) | ||
{ | ||
B = b; | ||
} | ||
public object[] B { get; } | ||
} | ||
|
||
[Mark(b: ""Hello"", a: true)] | ||
static class Program | ||
{ | ||
public static void Main() | ||
{ | ||
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>(); | ||
Console.Write(""Attribute constructor call: ""); | ||
PrintOutArgsInfo(attr.B); | ||
Console.Write(attr.B[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider verifying the length of the array and the value of |
||
} | ||
}", options: TestOptions.DebugExe); | ||
source.VerifyDiagnostics(); | ||
|
||
CompileAndVerify(source, expectedOutput: @"Hello"); | ||
} | ||
|
||
[Fact] | ||
[WorkItem(20741, "https://github.com/dotnet/roslyn/issues/20741")] | ||
public void TestNamedArgumentOnObjectParamsArgument3() | ||
{ | ||
var source = CreateCompilationWithMscorlib46(@" | ||
using System; | ||
using System.Reflection; | ||
|
||
sealed class MarkAttribute : Attribute | ||
{ | ||
public MarkAttribute(bool a, params object[] b) | ||
{ | ||
B = b; | ||
} | ||
public object[] B { get; } | ||
} | ||
|
||
private static void PrintOutArgsInfo(object[] args) | ||
[Mark(true, new object[] { ""Hello"" }, new object[] { ""World"" })] | ||
static class Program | ||
{ | ||
public static void Main() | ||
{ | ||
Console.WriteLine($""{args[0]}""); | ||
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>(); | ||
var worldArray = (object[])attr.B[1]; | ||
Console.Write(worldArray[0]); | ||
} | ||
}", options: TestOptions.DebugExe); | ||
source.VerifyDiagnostics(); | ||
|
||
CompileAndVerify(source, expectedOutput: | ||
@"Method call: Hello | ||
Attribute constructor call: Hello"); | ||
CompileAndVerify(source, expectedOutput: @"World"); | ||
} | ||
|
||
[Fact] | ||
|
@@ -146,39 +193,23 @@ sealed class MarkAttribute : Attribute | |
{ | ||
public MarkAttribute(int a, int b) | ||
{ | ||
A = a; | ||
B = b; | ||
} | ||
public int A { get; } | ||
public int B { get; } | ||
} | ||
|
||
[Mark(b: 42, a: 1)] | ||
static class Program | ||
{ | ||
private static void Test(int a, int b) | ||
=> PrintOutArgsInfo(b); | ||
|
||
public static void Main() | ||
{ | ||
Console.Write(""Method call: ""); | ||
Test(b: 42, a: 1); | ||
|
||
var attr = typeof(Program).GetCustomAttribute<MarkAttribute>(); | ||
Console.Write(""Attribute constructor call: ""); | ||
PrintOutArgsInfo(attr.B); | ||
} | ||
|
||
private static void PrintOutArgsInfo(int value) | ||
{ | ||
Console.WriteLine($""Value={value}""); | ||
Console.Write(attr.B); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider verifying the value of |
||
} | ||
}", options: TestOptions.DebugExe); | ||
source.VerifyDiagnostics(); | ||
|
||
CompileAndVerify(source, expectedOutput: | ||
@"Method call: Value=42 | ||
Attribute constructor call: Value=42"); | ||
CompileAndVerify(source, expectedOutput: "42"); | ||
} | ||
|
||
[WorkItem(984896, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/984896")] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does
TryGetNormalParamValue
handleargIndex = -1
? #ResolvedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's indeed a problem there. Thanks
Fixed in new commit #Resolved