Skip to content

Commit 2fed739

Browse files
eiriktsarpalisjeffhandley
authored andcommitted
Fix AIFunctionFactory handling of default struct arguments (#6381)
* Fix AIFunctionFactory handling of default struct arguments * Extend testing to custom structs * Use GetUninitializedObject instead of Activator
1 parent e0356e7 commit 2fed739

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -446,21 +446,23 @@ private static JsonElement ParseJsonElement(ReadOnlySpan<byte> utf8Json)
446446
return JsonElement.ParseValue(ref reader);
447447
}
448448

449+
[UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.",
450+
Justification = "Called conditionally on structs whose default ctor never gets trimmed.")]
449451
private static object? GetDefaultValueNormalized(ParameterInfo parameterInfo)
450452
{
451453
// Taken from https://github.com/dotnet/runtime/blob/eff415bfd667125c1565680615a6f19152645fbf/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs#L288-L317
452454
Type parameterType = parameterInfo.ParameterType;
453455
object? defaultValue = parameterInfo.DefaultValue;
454456

455-
if (defaultValue is null)
457+
if (defaultValue is null || (defaultValue == DBNull.Value && parameterType != typeof(DBNull)))
456458
{
457-
return null;
458-
}
459-
460-
// DBNull.Value is sometimes used as the default value (returned by reflection) of nullable params in place of null.
461-
if (defaultValue == DBNull.Value && parameterType != typeof(DBNull))
462-
{
463-
return null;
459+
return parameterType.IsValueType
460+
#if NET
461+
? RuntimeHelpers.GetUninitializedObject(parameterType)
462+
#else
463+
? System.Runtime.Serialization.FormatterServices.GetUninitializedObject(parameterType)
464+
#endif
465+
: null;
464466
}
465467

466468
// Default values of enums or nullable enums are represented using the underlying type and need to be cast explicitly

test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,17 @@ public async Task MarshalResult_TypeIsDeclaredTypeEvenWhenDerivedTypeReturned()
809809
Assert.Equal("marshalResultInvoked", result);
810810
}
811811

812+
[Fact]
813+
public async Task AIFunctionFactory_DefaultDefaultParameter()
814+
{
815+
Assert.NotEqual(new StructWithDefaultCtor().Value, default(StructWithDefaultCtor).Value);
816+
817+
AIFunction f = AIFunctionFactory.Create((Guid g = default, StructWithDefaultCtor s = default) => g.ToString() + "," + s.Value.ToString(), serializerOptions: JsonContext.Default.Options);
818+
819+
object? result = await f.InvokeAsync();
820+
Assert.Contains("00000000-0000-0000-0000-000000000000,0", result?.ToString());
821+
}
822+
812823
private sealed class MyService(int value)
813824
{
814825
public int Value => value;
@@ -871,7 +882,19 @@ private class A;
871882
private class B : A;
872883
private sealed class C : B;
873884

885+
public readonly struct StructWithDefaultCtor
886+
{
887+
public int Value { get; }
888+
public StructWithDefaultCtor()
889+
{
890+
Value = 42;
891+
}
892+
}
893+
874894
[JsonSerializable(typeof(IAsyncEnumerable<int>))]
875895
[JsonSerializable(typeof(int[]))]
896+
[JsonSerializable(typeof(string))]
897+
[JsonSerializable(typeof(Guid))]
898+
[JsonSerializable(typeof(StructWithDefaultCtor))]
876899
private partial class JsonContext : JsonSerializerContext;
877900
}

0 commit comments

Comments
 (0)