diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs index f0764d6fae8..a0afa66f98c 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.cs @@ -446,21 +446,23 @@ private static JsonElement ParseJsonElement(ReadOnlySpan utf8Json) return JsonElement.ParseValue(ref reader); } + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", + Justification = "Called conditionally on structs whose default ctor never gets trimmed.")] private static object? GetDefaultValueNormalized(ParameterInfo parameterInfo) { // Taken from https://github.com/dotnet/runtime/blob/eff415bfd667125c1565680615a6f19152645fbf/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs#L288-L317 Type parameterType = parameterInfo.ParameterType; object? defaultValue = parameterInfo.DefaultValue; - if (defaultValue is null) + if (defaultValue is null || (defaultValue == DBNull.Value && parameterType != typeof(DBNull))) { - return null; - } - - // DBNull.Value is sometimes used as the default value (returned by reflection) of nullable params in place of null. - if (defaultValue == DBNull.Value && parameterType != typeof(DBNull)) - { - return null; + return parameterType.IsValueType +#if NET + ? RuntimeHelpers.GetUninitializedObject(parameterType) +#else + ? System.Runtime.Serialization.FormatterServices.GetUninitializedObject(parameterType) +#endif + : null; } // Default values of enums or nullable enums are represented using the underlying type and need to be cast explicitly diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs index 0670a06b206..4b5ff9a0600 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs @@ -809,6 +809,17 @@ public async Task MarshalResult_TypeIsDeclaredTypeEvenWhenDerivedTypeReturned() Assert.Equal("marshalResultInvoked", result); } + [Fact] + public async Task AIFunctionFactory_DefaultDefaultParameter() + { + Assert.NotEqual(new StructWithDefaultCtor().Value, default(StructWithDefaultCtor).Value); + + AIFunction f = AIFunctionFactory.Create((Guid g = default, StructWithDefaultCtor s = default) => g.ToString() + "," + s.Value.ToString(), serializerOptions: JsonContext.Default.Options); + + object? result = await f.InvokeAsync(); + Assert.Contains("00000000-0000-0000-0000-000000000000,0", result?.ToString()); + } + private sealed class MyService(int value) { public int Value => value; @@ -871,7 +882,19 @@ private class A; private class B : A; private sealed class C : B; + public readonly struct StructWithDefaultCtor + { + public int Value { get; } + public StructWithDefaultCtor() + { + Value = 42; + } + } + [JsonSerializable(typeof(IAsyncEnumerable))] [JsonSerializable(typeof(int[]))] + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(Guid))] + [JsonSerializable(typeof(StructWithDefaultCtor))] private partial class JsonContext : JsonSerializerContext; }