Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5d2bccf
deps: Bump the build-tools group with 3 updates
dependabot[bot] Apr 20, 2026
05f7b9d
Bump transitive BCL package versions for Roslyn 5.3.0 and ignore CS17…
eiriktsarpalis Apr 20, 2026
ebe575c
Bump JsonSchema.Net to 9.2.0 and update for new API
eiriktsarpalis Apr 20, 2026
d4baecf
Merge remote-tracking branch 'origin/main' into pr-418
eiriktsarpalis Apr 20, 2026
6312d5a
Remove CS1701/CS1702 suppressions
eiriktsarpalis Apr 20, 2026
b1f503c
Address format-validation by declaring $schema dialect
eiriktsarpalis Apr 20, 2026
77cfccb
Emit $schema dialect from JsonSchemaGenerator
eiriktsarpalis Apr 20, 2026
a876d85
Inline $schema dialect emission via depth tracking
eiriktsarpalis Apr 20, 2026
5f08442
Construct $schema dialect at the point of root JsonObject creation
eiriktsarpalis Apr 20, 2026
5f1aaa2
Remove `$schema` mutations from JsonSchema test assertions
eiriktsarpalis Apr 20, 2026
c91a4f1
Simplify $schema dialect emission via JsonObject.Insert
eiriktsarpalis Apr 20, 2026
39d74ea
Reference System.Text.Json on net8.0 to enable JsonObject.Insert
eiriktsarpalis Apr 20, 2026
6bb34eb
Merge ApplyNullability and InsertDialectIfRoot into CompleteDocument
eiriktsarpalis Apr 20, 2026
5f949af
Drop CompleteDocument implementation comments
eiriktsarpalis Apr 20, 2026
40bc6f4
Make MetaSchemaUri private
eiriktsarpalis Apr 20, 2026
081d6d5
Share a single Generator instance across IMethodShape sub-schemas
eiriktsarpalis Apr 20, 2026
9791b53
Revert RoslynVersion to 4.8.0 to keep VS 2022 source generator support
eiriktsarpalis Apr 20, 2026
951fa7f
Scope JsonSchema.Net and BCL 10 deps to PolyType.Tests
eiriktsarpalis Apr 20, 2026
785767e
Move tests Directory.Packages.props up to tests/
eiriktsarpalis Apr 20, 2026
aa4f4e6
Move test-only package versions out of the root central props
eiriktsarpalis Apr 20, 2026
5a91079
Bump System.* packages to .NET 10 baseline
eiriktsarpalis Apr 20, 2026
46ac3c5
Document and trim Benchmarks scoped Directory.Packages.props
eiriktsarpalis Apr 20, 2026
b40507f
Pin Microsoft.Extensions.Configuration to 9.0.8
eiriktsarpalis Apr 20, 2026
315bc8a
Simplify JsonSchema test helper by stripping $schema before DeepEquals
eiriktsarpalis Apr 21, 2026
9c7ad22
Restore original Push/Pop method positions
eiriktsarpalis Apr 21, 2026
9f9c0a5
Replace project-scoped CPM with VersionOverride in Benchmarks csproj
eiriktsarpalis Apr 21, 2026
f580da7
Restore conservative product dependency versions in root props
eiriktsarpalis Apr 21, 2026
5e0c64e
Soften MEC pin comment (no documented breaking change)
eiriktsarpalis Apr 21, 2026
2c84fca
Cite official .NET 10 breaking change in MEC pin comment
eiriktsarpalis Apr 21, 2026
4a1f88c
Adopt Microsoft.Extensions.Configuration 10.x semantics
eiriktsarpalis Apr 21, 2026
40582d2
Move Roslyn version override into tests/Directory.Packages.props
eiriktsarpalis Apr 21, 2026
5ee746c
Bump central System.* package versions, pin core PolyType conservatively
eiriktsarpalis Apr 21, 2026
1f32212
Disable global transitive pinning for package compatibility
eiriktsarpalis Apr 21, 2026
e3f74cf
Fix netfx unit test restore
eiriktsarpalis Apr 21, 2026
163f577
Fix netfx tests without warning suppression
eiriktsarpalis Apr 21, 2026
0537dd1
Fix up
eiriktsarpalis Apr 21, 2026
0826f4e
Remove unnecesary dependency
eiriktsarpalis Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 23 additions & 25 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,54 +1,52 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<RoslynVersion>4.8.0</RoslynVersion>
<RoslynVersion>5.3.0</RoslynVersion>
<RoslynVersionForAnalyzers>4.3.0</RoslynVersionForAnalyzers>
</PropertyGroup>
<ItemGroup>
<!-- Product dependencies -->
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Memory" Version="4.5.4" />
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<!-- Source Generator dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(RoslynVersion)" />
<!-- Build Infra & Packaging -->
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.10.8-alpha" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Testing dependencies -->
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
<PackageVersion Include="TUnit" Version="1.12.65" />
<PackageVersion Include="TUnit.Assertions" Version="1.12.65" />
<PackageVersion Include="TUnit.Engine" Version="1.12.65" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.8" />
<PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="2.0.2" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.3.2" />
<PackageVersion Include="Microsoft.Testing.Extensions.CrashDump" Version="2.0.2" />
<PackageVersion Include="Microsoft.Testing.Extensions.HangDump" Version="2.0.2" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<!-- Earliest version of the library supporting literals and frozen collections -->
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Formats.Cbor" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<!-- Used by PolyType.Examples and PolyType.TestCases -->
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.5" />
<PackageVersion Include="System.Formats.Cbor" Version="10.0.5" />
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
<PackageVersion Include="YamlDotNet" Version="17.0.1" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="JsonSchema.Net" Version="7.4.0" />
<PackageVersion Include="FSharp.Core" Version="10.1.202" />
<!-- Test infrastructure -->
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
<PackageVersion Include="TUnit" Version="1.37.10" />
<PackageVersion Include="TUnit.Assertions" Version="1.37.10" />
<PackageVersion Include="TUnit.Engine" Version="1.37.10" />
<PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="2.2.1" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.6.2" />
<PackageVersion Include="Microsoft.Testing.Extensions.CrashDump" Version="2.2.1" />
<PackageVersion Include="Microsoft.Testing.Extensions.HangDump" Version="2.2.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="JsonSchema.Net" Version="9.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(IsAnalyzerProject)'=='true'">
<!-- Keep these versions in sync with what Unity documents as supported at
https://docs.unity3d.com/6000.1/Documentation/Manual/create-source-generator.html (or a newer version of that doc). -->
<PackageVersion Update="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Update="Microsoft.CodeAnalysis.CSharp" Version="$(RoslynVersionForAnalyzers)" />
<PackageVersion Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(RoslynVersionForAnalyzers)" />
<PackageVersion Update="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(RoslynVersionForAnalyzers)" />
<PackageVersion Update="System.Collections.Immutable" Version="6.0.0" />
<PackageVersion Update="System.Memory" Version="4.5.4" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -313,15 +313,15 @@ private static IEnumerable<KeyValuePair<Type, object>> GetBuiltInParsers()
yield return Create(text => float.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture));
yield return Create(text => double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture));
yield return Create(text => decimal.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture));
yield return Create(text => string.IsNullOrEmpty(text) ? null : text);
yield return Create<string?>(text => text);
yield return Create(char.Parse);
yield return Create(Guid.Parse);
yield return Create(text => TimeSpan.Parse(text, CultureInfo.InvariantCulture));
yield return Create(text => DateTime.Parse(text, CultureInfo.InvariantCulture));
yield return Create(text => DateTimeOffset.Parse(text, CultureInfo.InvariantCulture));
yield return Create(text => text is "" ? null : new Uri(text, UriKind.RelativeOrAbsolute));
yield return Create(text => text is "" ? null : Version.Parse(text));
yield return Create(text => text is "" ? null : Convert.FromBase64String(text));
yield return Create(text => new Uri(text, UriKind.RelativeOrAbsolute));
yield return Create(Version.Parse);
yield return Create(Convert.FromBase64String);
#if NET
yield return Create(text => UInt128.Parse(text, NumberStyles.Integer, CultureInfo.InvariantCulture));
yield return Create(text => Int128.Parse(text, NumberStyles.Integer, CultureInfo.InvariantCulture));
Expand All @@ -332,8 +332,6 @@ private static IEnumerable<KeyValuePair<Type, object>> GetBuiltInParsers()
#endif

yield return Create<object?>(text =>
text is null ? new object() :
text is "" ? null :
bool.TryParse(text, out bool boolResult) ? boolResult :
int.TryParse(text, out int intResult) ? intResult :
double.TryParse(text, out double doubleResult) ? doubleResult :
Expand Down Expand Up @@ -367,6 +365,11 @@ private static Func<IConfiguration, T> CreateValueBinder<T>(Func<string, T> pars
throw new InvalidOperationException();
}

if (section.Value is null && default(T) is null)
{
return default!;
}

try
{
return parser(section.Value!);
Expand All @@ -379,11 +382,10 @@ private static Func<IConfiguration, T> CreateValueBinder<T>(Func<string, T> pars
}

private static Func<IConfiguration, T> CreateNotSupportedBinder<T>() =>
config => default(T) is null && IsNullConfiguration(config) ? default! : throw new NotSupportedException($"Type '{typeof(T)}' is not supported.");
config => IsNullConfiguration(config) ? default! : throw new NotSupportedException($"Type '{typeof(T)}' is not supported.");

private static bool IsNullConfiguration(IConfiguration configuration) =>
// https://github.com/dotnet/runtime/issues/36510
configuration is IConfigurationSection { Value: "" } &&
configuration is IConfigurationSection { Value: null } &&
!configuration.GetChildren().Any();
}

Expand Down
114 changes: 65 additions & 49 deletions src/PolyType.Examples/JsonSchema/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public static class JsonSchemaGenerator
public static JsonObject Generate<T>(ITypeShapeProvider typeShapeProvider)
=> Generate(typeShapeProvider.GetTypeShapeOrThrow<T>());

private const string MetaSchemaUri = "https://json-schema.org/draft/2020-12/schema";

/// <summary>
/// Generates a JSON schema using the specified shape.
/// </summary>
Expand All @@ -28,42 +30,7 @@ public static JsonObject Generate(ITypeShape typeShape)
/// Generates a JSON schema using the specified method shape.
/// </summary>
public static JsonObject Generate(IMethodShape methodShape)
{
JsonObject? parameterSchemas = null;
JsonArray? requiredParams = null;
foreach (var parameter in methodShape.Parameters)
{
if (parameter.ParameterType.Type == typeof(CancellationToken))
{
continue;
}

(parameterSchemas ??= []).Add(parameter.Name, Generate(parameter.ParameterType));
if (parameter.IsRequired)
{
(requiredParams ??= []).Add((JsonNode)parameter.Name);
}
}

JsonObject functionSchema = new JsonObject
{
["name"] = methodShape.Name,
["type"] = "object",
};

if (parameterSchemas is not null)
{
functionSchema["properties"] = parameterSchemas;
}

if (requiredParams is not null)
{
functionSchema["required"] = requiredParams;
}

functionSchema["output"] = Generate(methodShape.ReturnType);
return functionSchema;
}
=> new Generator().GenerateMethodSchema(methodShape);

#if NET
/// <summary>
Expand All @@ -87,13 +54,57 @@ private sealed class Generator
private readonly Dictionary<(Type, bool AllowNull), string> _locations = new();
private readonly List<string> _path = new();

public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bool cacheLocation = true)
public JsonObject GenerateMethodSchema(IMethodShape methodShape)
{
JsonObject? parameterSchemas = null;
JsonArray? requiredParams = null;
foreach (var parameter in methodShape.Parameters)
{
if (parameter.ParameterType.Type == typeof(CancellationToken))
{
continue;
}

Push("properties");
Push(parameter.Name);
(parameterSchemas ??= []).Add(parameter.Name, GenerateSchema(parameter.ParameterType, depth: 1));
Pop();
Pop();
if (parameter.IsRequired)
{
(requiredParams ??= []).Add((JsonNode)parameter.Name);
}
}

JsonObject functionSchema = new JsonObject
{
["name"] = methodShape.Name,
["type"] = "object",
};

if (parameterSchemas is not null)
{
functionSchema["properties"] = parameterSchemas;
}

if (requiredParams is not null)
{
functionSchema["required"] = requiredParams;
}

Push("output");
functionSchema["output"] = GenerateSchema(methodShape.ReturnType, depth: 1);
Pop();
return CompleteDocument(functionSchema, allowNull: false, depth: 0);
}

public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bool cacheLocation = true, int depth = 0)
{
allowNull = allowNull && IsNullableType(typeShape.Type);

if (s_simpleTypeInfo.TryGetValue(typeShape.Type, out SimpleTypeJsonSchema simpleType))
{
return ApplyNullability(simpleType.ToSchemaDocument(), allowNull);
return CompleteDocument(simpleType.ToSchemaDocument(), allowNull, depth);
}

if (cacheLocation)
Expand Down Expand Up @@ -125,20 +136,20 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo
break;

case IOptionalTypeShape optionalShape:
schema = GenerateSchema(optionalShape.ElementType, cacheLocation: false);
ApplyNullability(schema, allowNull: true);
schema = GenerateSchema(optionalShape.ElementType, cacheLocation: false, depth: depth + 1);
allowNull = true;
break;

case ISurrogateTypeShape surrogateShape:
return GenerateSchema(surrogateShape.SurrogateType, cacheLocation: false);
return CompleteDocument(GenerateSchema(surrogateShape.SurrogateType, cacheLocation: false, depth: depth + 1), allowNull: false, depth);

case IEnumerableTypeShape enumerableShape:
for (int i = 0; i < enumerableShape.Rank; i++)
{
Push("items");
}

schema = GenerateSchema(enumerableShape.ElementType);
schema = GenerateSchema(enumerableShape.ElementType, depth: depth + 1);

for (int i = 0; i < enumerableShape.Rank; i++)
{
Expand All @@ -155,7 +166,7 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo

case IDictionaryTypeShape dictionaryShape:
Push("additionalProperties");
JsonObject additionalPropertiesSchema = GenerateSchema(dictionaryShape.ValueType);
JsonObject additionalPropertiesSchema = GenerateSchema(dictionaryShape.ValueType, depth: depth + 1);
Pop();

schema = new JsonObject
Expand Down Expand Up @@ -191,7 +202,7 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo
(associatedParameter is null || associatedParameter.IsNonNullable);

Push(prop.Name);
JsonObject propSchema = GenerateSchema(prop.PropertyType, allowNull: !isNonNullable);
JsonObject propSchema = GenerateSchema(prop.PropertyType, allowNull: !isNonNullable, depth: depth + 1);
Pop();

properties.Add(prop.Name, propSchema);
Expand Down Expand Up @@ -220,7 +231,7 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo
foreach (IUnionCaseShape caseShape in unionShape.UnionCases)
{
Push($"{anyOf.Count}");
JsonObject caseSchema = GenerateSchema(caseShape.UnionCaseType, cacheLocation: false);
JsonObject caseSchema = GenerateSchema(caseShape.UnionCaseType, cacheLocation: false, depth: depth + 1);
Pop();

if (caseShape.UnionCaseType is IObjectTypeShape or IDictionaryTypeShape)
Expand Down Expand Up @@ -274,7 +285,7 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo
if (!unionCasesContainBaseType)
{
Push($"{anyOf.Count}");
JsonNode caseSchema = GenerateSchema(unionShape.BaseType, cacheLocation: false);
JsonNode caseSchema = GenerateSchema(unionShape.BaseType, cacheLocation: false, depth: depth + 1);
Pop();

anyOf.Add(caseSchema);
Expand All @@ -293,7 +304,7 @@ public JsonObject GenerateSchema(ITypeShape typeShape, bool allowNull = true, bo
break;
}

return ApplyNullability(schema, allowNull);
return CompleteDocument(schema, allowNull, depth);
}

private void Push(string name)
Expand All @@ -306,11 +317,11 @@ private void Pop()
_path.RemoveAt(_path.Count - 1);
}

private static JsonObject ApplyNullability(JsonObject schema, bool allowNull)
private static JsonObject CompleteDocument(JsonObject schema, bool allowNull, int depth)
{
if (allowNull && schema.TryGetPropertyValue("type", out JsonNode? typeValue))
{
if (schema["type"] is JsonArray types)
if (typeValue is JsonArray types)
{
types.Add((JsonNode)"null");
}
Expand All @@ -320,6 +331,11 @@ private static JsonObject ApplyNullability(JsonObject schema, bool allowNull)
}
}

if (depth == 0)
{
schema.Insert(0, "$schema", MetaSchemaUri);
}

return schema;
}

Expand Down
5 changes: 4 additions & 1 deletion src/PolyType.Examples/PolyType.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@
<PackageReference Include="YamlDotNet" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net10.0'))">
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.HashCode" />
</ItemGroup>

Expand Down
8 changes: 0 additions & 8 deletions tests/PolyType.Benchmarks/Directory.Packages.props

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,18 @@ public static Compilation CreateCompilation(
.. additionalReferences,
];

var options = new CSharpCompilationOptions(outputKind, nullableContextOptions: nullableContextOptions, allowUnsafe: true);
#if !NET
// On .NET Framework the test process may load newer BCL assemblies (e.g. System.Memory)
// than those PolyType was compiled against. CS1702 warns about the version mismatch,
// which is benign in an in-memory compilation used for source-generator testing.
options = options.WithSpecificDiagnosticOptions([new("CS1702", ReportDiagnostic.Suppress)]);
#endif
return CSharpCompilation.Create(
assemblyName,
syntaxTrees: syntaxTrees,
references: references,
options: new CSharpCompilationOptions(outputKind, nullableContextOptions: nullableContextOptions, allowUnsafe: true)
options: options
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Loading
Loading