Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattiasnordqvist committed Nov 10, 2024
1 parent 08ef0e8 commit 59edee0
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class Mappers
var isNamePropertyDefined = jsonElement.TryGetProperty("Name", out var jsonNameProperty);
if (isNamePropertyDefined)
{
// String, isOption = False
// type = String, isOption = False, isNullable = False
if (jsonNameProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Name"]));
Expand All @@ -42,84 +42,6 @@ public static partial class Mappers
{
errors.Add(new RequiredPropertyMissingError([.. path, "Name"]));
}
var isAgePropertyDefined = jsonElement.TryGetProperty("Age", out var jsonAgeProperty);
if (isAgePropertyDefined)
{
// Int32, isOption = False
if (jsonAgeProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Age"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "Age"]));
}
var isIsSubscribedPropertyDefined = jsonElement.TryGetProperty("IsSubscribed", out var jsonIsSubscribedProperty);
if (isIsSubscribedPropertyDefined)
{
// Boolean, isOption = False
if (jsonIsSubscribedProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "IsSubscribed"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "IsSubscribed"]));
}
var isCoursesPropertyDefined = jsonElement.TryGetProperty("Courses", out var jsonCoursesProperty);
if (isCoursesPropertyDefined)
{
// , isOption = False
if (jsonCoursesProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Courses"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "Courses"]));
}
var isPetPropertyDefined = jsonElement.TryGetProperty("Pet", out var jsonPetProperty);
if (isPetPropertyDefined)
{
// Pet, isOption = False
if (jsonPetProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Pet"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "Pet"]));
}
var isIsGayPropertyDefined = jsonElement.TryGetProperty("IsGay", out var jsonIsGayProperty);
if (isIsGayPropertyDefined)
{
// Nullable, isOption = False
if (jsonIsGayProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "IsGay"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "IsGay"]));
}
var isFavoritePetPropertyDefined = jsonElement.TryGetProperty("FavoritePet", out var jsonFavoritePetProperty);
if (isFavoritePetPropertyDefined)
{
// Pet, isOption = False
if (jsonFavoritePetProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "FavoritePet"]));
}
}
else
{
errors.Add(new RequiredPropertyMissingError([.. path, "FavoritePet"]));
}
if(errors.Any())
{
return Result<FartingUnicorn.Benchmarks.UserProfile>.Error(errors);
Expand Down
22 changes: 7 additions & 15 deletions FartingUnicorn.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public static void Main(string[] args)
public class UserProfile
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsSubscribed { get; set; }
public string[] Courses { get; set; }
public Pet Pet { get; set; }
//public int Age { get; set; }
//public bool IsSubscribed { get; set; }
//public string[] Courses { get; set; }
//public Pet Pet { get; set; }

public bool? IsGay { get; set; }
public Pet? FavoritePet { get; set; }
//public bool? IsGay { get; set; }
//public Pet? FavoritePet { get; set; }
}

public class Pet
Expand All @@ -41,15 +41,7 @@ public class SerializationBenchmarks

private static string _json = """
{
"Name": "John Doe",
"Age": 30,
"IsSubscribed": true,
"Courses": ["Math", "Science"],
"Pet": {
"Name": "Fluffy",
"Type": "Dog"
},
"IsGay": true
"Name": "John Doe"
}
""";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class Mappers
var isTitlePropertyDefined = jsonElement.TryGetProperty("Title", out var jsonTitleProperty);
if (isTitlePropertyDefined)
{
// String, isOption = False
// type = String, isOption = False, isNullable = False
if (jsonTitleProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Title"]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class Mappers
var isTitlePropertyDefined = jsonElement.TryGetProperty("Title", out var jsonTitleProperty);
if (isTitlePropertyDefined)
{
// String, isOption = True
// type = String, isOption = True, isNullable = False
if (jsonTitleProperty.ValueKind == JsonValueKind.Null)
{
obj.Title = new None<String>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class Mappers
var isTitlePropertyDefined = jsonElement.TryGetProperty("Title", out var jsonTitleProperty);
if (isTitlePropertyDefined)
{
// String, isOption = False
// type = String, isOption = False, isNullable = True
if (jsonTitleProperty.ValueKind == JsonValueKind.Null)
{
errors.Add(new RequiredValueMissingError([.. path, "Title"]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class Mappers
var isTitlePropertyDefined = jsonElement.TryGetProperty("Title", out var jsonTitleProperty);
if (isTitlePropertyDefined)
{
// String, isOption = True
// type = String, isOption = True, isNullable = True
if (jsonTitleProperty.ValueKind == JsonValueKind.Null)
{
obj.Title = new None<String>();
Expand Down
39 changes: 39 additions & 0 deletions MapperGenerator/CodeAnalysisExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.CodeAnalysis;

using System.Diagnostics;

namespace MapperGenerator;

public static partial class CodeAnalysisExtensions
{
public static bool IsNullable(this ITypeSymbol typeSymbol) =>
typeSymbol.NullableAnnotation == NullableAnnotation.Annotated;

public static bool IsNullableValueType(this ITypeSymbol typeSymbol) =>
typeSymbol.IsValueType && typeSymbol.IsNullable();

public static bool TryGetNullableValueUnderlyingType(this ITypeSymbol typeSymbol, out ITypeSymbol? underlyingType)
{
if (typeSymbol is INamedTypeSymbol namedType && typeSymbol.IsNullableValueType() && namedType.IsGenericType)
{
var typeParameters = namedType.TypeArguments;
// Assert the generic is named System.Nullable<T> as expected.
Debug.Assert(namedType.ConstructUnboundGenericType() is { } genericType && genericType.Name == "Nullable" && genericType.ContainingNamespace.Name == "System" && genericType.TypeArguments.Length == 1);
Debug.Assert(typeParameters.Length == 1);
underlyingType = typeParameters[0];
// TODO: decide what to return when the underlying type is not declared due to some compilation error.
// TypeKind.Error indicats a compilation error, specifically a nullable type where the underlying type was not found.
// I have observed that IsValueType will be true in such cases even though it is actually unknown whether the missing type is a value type
// I chose to return false but you may prefer something else.
return underlyingType.TypeKind == TypeKind.Error ? false : true;
}
underlyingType = null;
return false;
}

public static bool IsEnum(this ITypeSymbol typeSymbol) =>
typeSymbol is INamedTypeSymbol namedType && namedType.EnumUnderlyingType != null;

public static bool IsNullableEnumType(this ITypeSymbol typeSymbol) =>
typeSymbol.TryGetNullableValueUnderlyingType(out var underlyingType) == true && underlyingType.IsEnum();
}
25 changes: 8 additions & 17 deletions MapperGenerator/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System.Collections;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Text;

Expand Down Expand Up @@ -66,7 +65,7 @@ public static SourceText GenerateExtensionClass(ClassToGenerateMapperFor classTo
sb.AppendLine($"if (is{p.Name}PropertyDefined)");
using (var _3 = sb.CodeBlock())
{
sb.AppendLine($"// {p.Type}, isOption = {p.IsOption}");
sb.AppendLine($"// type = {p.Type}, isOption = {p.IsOption}, isNullable = {p.IsNullable}");
sb.AppendLine($"if (json{p.Name}Property.ValueKind == JsonValueKind.Null)");
using (var _4 = sb.CodeBlock())
{
Expand Down Expand Up @@ -158,9 +157,7 @@ static void Execute(ClassToGenerateMapperFor? classToGenerateMapperFor, SourcePr
{
if (classToGenerateMapperFor is { } value)
{
// generate the source code and add it to the output
var sourceText = SourceGenerationHelper.GenerateExtensionClass(value);
// Create a separate partial class file for each enum
context.AddSource($"Mapper.{value.FullName}.g.cs", sourceText);
}
}
Expand All @@ -169,10 +166,8 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node)

static ClassToGenerateMapperFor? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
// we know the node is a EnumDeclarationSyntax thanks to IsSyntaxTargetForGeneration
var classDeclarationSyntaxNode = (ClassDeclarationSyntax)context.Node;

// loop through all the attributes on the method
foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntaxNode.AttributeLists)
{
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
Expand All @@ -186,36 +181,27 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node)
INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
string fullName = attributeContainingTypeSymbol.ToDisplayString();

// Is the attribute the [EnumExtensions] attribute?
if (fullName == "DotNetThoughts.FartingUnicorn.CreateMapperAttribute")
{
// return the enum. Implementation shown in section 7.
return GetClassToGenerate(context.SemanticModel, classDeclarationSyntaxNode);
}
}
}

// we didn't find the attribute we were looking for
return null;
}

static ClassToGenerateMapperFor? GetClassToGenerate(SemanticModel semanticModel, SyntaxNode enumDeclarationSyntax)
{
// Get the semantic representation of the enum syntax
if (semanticModel.GetDeclaredSymbol(enumDeclarationSyntax) is not INamedTypeSymbol classSymbol)
{
// something went wrong
return null;
}

// Get the full type name of the enum e.g. Colour,
// or OuterClass<T>.Colour if it was nested in a generic type (for example)
string className = classSymbol.ToString();
string name = classSymbol.Name.ToString();




// Get all the members in the enum
ImmutableArray<ISymbol> members = classSymbol.GetMembers();
var properties = new List<PropertyToGenerateMapperFor>(members.Length);
Expand All @@ -230,7 +216,10 @@ static bool IsSyntaxTargetForGeneration(SyntaxNode node)
var tName = !isOptions
? p.Type.Name
: ((INamedTypeSymbol)p.Type).TypeArguments.First().Name;
properties.Add(new PropertyToGenerateMapperFor(p.Name, tName, isOptions));

var isNullable = t.IsNullable();

properties.Add(new PropertyToGenerateMapperFor(p.Name, tName, isOptions, isNullable));
}
}

Expand All @@ -243,11 +232,13 @@ public readonly record struct PropertyToGenerateMapperFor
public readonly string Name;
public readonly string Type;
public readonly bool IsOption;
public PropertyToGenerateMapperFor(string name, string type, bool isOption)
public readonly bool IsNullable;
public PropertyToGenerateMapperFor(string name, string type, bool isOption, bool isNullable)
{
Name = name;
Type = type;
IsOption = isOption;
IsNullable = isNullable;
}

}
Expand Down

0 comments on commit 59edee0

Please sign in to comment.