Skip to content

Commit ff3cc72

Browse files
committed
Fix #886: use default instead of null for optional struct ByRef parameters
When a VB optional ByRef parameter has a struct type with `Nothing` as default, Roslyn returns null from ExplicitDefaultValue. Generating a null literal causes a C# compile error since structs cannot be null. Use `default` instead when the parameter type is a value type and the default value is null. https://claude.ai/code/session_01AkwUvu3XuCdj3D4axoX4UX
1 parent 0e1ce42 commit ff3cc72

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

CodeConverter/CSharp/ArgumentConverter.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ private CSSyntax.ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p, RefKind
226226
var type = CommonConversions.GetTypeSyntax(p.Type);
227227
CSSyntax.ExpressionSyntax initializer;
228228
if (p.HasExplicitDefaultValue) {
229-
initializer = CommonConversions.Literal(p.ExplicitDefaultValue);
229+
initializer = LiteralOrDefault(p.ExplicitDefaultValue, p.Type);
230230
} else if (HasOptionalAttribute(p)) {
231231
if (TryGetDefaultParameterValueAttributeValue(p, out var defaultValue)) {
232-
initializer = CommonConversions.Literal(defaultValue);
232+
initializer = LiteralOrDefault(defaultValue, p.Type);
233233
} else {
234234
initializer = CS.SyntaxFactory.DefaultExpression(type);
235235
}
@@ -273,6 +273,18 @@ bool TryGetDefaultParameterValueAttributeValue(IParameterSymbol p, out object de
273273
}
274274
}
275275

276+
/// <summary>
277+
/// Returns a literal expression for the given value, or a <c>default</c> expression when the value is null
278+
/// and the parameter type is a value type (e.g. a struct), since null is not a valid C# initializer for value types.
279+
/// </summary>
280+
private static CSSyntax.ExpressionSyntax LiteralOrDefault(object value, ITypeSymbol paramType)
281+
{
282+
if (value is null && paramType.IsValueType) {
283+
return ValidSyntaxFactory.DefaultExpression;
284+
}
285+
return CommonConversions.Literal(value);
286+
}
287+
276288
public CSSyntax.ArgumentListSyntax CreateArgList(ISymbol invocationSymbol)
277289
{
278290
return CS.SyntaxFactory.ArgumentList(CS.SyntaxFactory.SeparatedList(

Tests/CSharp/ExpressionTests/ByRefTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,4 +914,41 @@ public static void LogAndReset(ref int arg)
914914
}");
915915
}
916916

917+
[Fact]
918+
public async Task OptionalStructRefParameterUsesDefaultNotNullIssue886Async()
919+
{
920+
await TestConversionVisualBasicToCSharpAsync(@"Public Class Issue886
921+
Private Shared Sub OptionalParams()
922+
FunctionWithOptionalParams()
923+
End Sub
924+
925+
Private Shared Sub FunctionWithOptionalParams(Optional ByRef structParam As TestStruct = Nothing)
926+
structParam = New TestStruct
927+
End Sub
928+
929+
Friend Structure TestStruct
930+
Friend A As Boolean
931+
End Structure
932+
End Class", @"using System.Runtime.InteropServices;
933+
934+
public partial class Issue886
935+
{
936+
private static void OptionalParams()
937+
{
938+
TestStruct argstructParam = default;
939+
FunctionWithOptionalParams(structParam: ref argstructParam);
940+
}
941+
942+
private static void FunctionWithOptionalParams([Optional] ref TestStruct structParam)
943+
{
944+
structParam = new TestStruct();
945+
}
946+
947+
internal struct TestStruct
948+
{
949+
internal bool A;
950+
}
951+
}");
952+
}
953+
917954
}

0 commit comments

Comments
 (0)