Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions Bond.Build/GenerateBond.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ private void Generate(ITaskItem source, string path, string output)
UsingNamespaces = List(source, "Usings"),
NamespaceMappings = List(source, "NamespaceMappings"),
TypeMappings = List(source, "TypeMappings"),
ModelFeatures = Feature(source, "Clone", CSharpModelFeatures.Cloning)
| Feature(source, "Equality", CSharpModelFeatures.Equality)
| Feature(source, "ToString", CSharpModelFeatures.StringRepresentation)
ModelFeatures = (IsEnabled(source, "Clone") ? CSharpModelFeatures.Cloning : 0)
| (IsEnabled(source, "Equality") ? CSharpModelFeatures.Equality : 0)
| (IsEnabled(source, "ToString") ? CSharpModelFeatures.StringRepresentation : 0),
Serialization = IsEnabled(source, "Serialization")
};
var imports = List(source, "ImportDirectories").Select(directory => Path.GetFullPath(directory, ProjectDirectory));
var parsed = ParserFacade.ParseFileAsync(path, DefaultImportResolver.Create(imports)).GetAwaiter().GetResult();
Expand All @@ -92,8 +93,8 @@ private static string OutputName(string source) =>
private static string[] List(ITaskItem item, string name) =>
item.GetMetadata(name).Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

private static CSharpModelFeatures Feature(ITaskItem item, string name, CSharpModelFeatures feature) =>
string.Equals(item.GetMetadata(name), "true", StringComparison.OrdinalIgnoreCase) ? feature : CSharpModelFeatures.None;
private static bool IsEnabled(ITaskItem item, string name) =>
string.Equals(item.GetMetadata(name), "true", StringComparison.OrdinalIgnoreCase);

// Task item specs are MSBuild-escaped; literal paths may contain characters such as ';' or '%'.
private static string Escape(string path) =>
Expand Down
2 changes: 1 addition & 1 deletion Bond.Build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Generates C# for `Bond` items during `dotnet build` (.NET 8+ SDK).

```xml
<PropertyGroup>
<!-- Optional defaults; each can also be set per item as metadata, e.g. <Bond Include="..." Clone="true" />. -->
<BondClone>true</BondClone>
<BondEquality>true</BondEquality>
<BondToString>true</BondToString>
<BondSerialization>true</BondSerialization>
<BondImportDirectories>Schemas/Shared</BondImportDirectories>
<BondUsings>System.Collections.Generic</BondUsings>
<BondNamespaceMappings>Contracts=MyApp.Contracts</BondNamespaceMappings>
Expand Down
1 change: 1 addition & 0 deletions Bond.Build/build/BondTools.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<Clone>$(BondClone)</Clone>
<Equality>$(BondEquality)</Equality>
<ToString>$(BondToString)</ToString>
<Serialization>$(BondSerialization)</Serialization>
</Bond>
</ItemDefinitionGroup>

Expand Down
6 changes: 4 additions & 2 deletions Bond.Parser.CLI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ bond generate csharp <schema.bond>... -o <dir> [options]
--clone Generate deep Clone()
--equality Generate structural Equals() and GetHashCode()
--to-string Generate ToString()
--serialization Generate Compact Binary serialization (requires BondTools.Runtime)

Exit codes:
0 success
Expand Down Expand Up @@ -145,7 +146,7 @@ private static async Task<int> Format(string[] rest)

private static async Task<int> Generate(string[] rest)
{
var args = Arguments.Parse(rest, ["--clone", "--equality", "--to-string"],
var args = Arguments.Parse(rest, ["--clone", "--equality", "--to-string", "--serialization"],
["--output-dir", "--import-dir", "--namespace", "--using", "--type-map", "--error-format"]);
if (args.Has("--help"))
{
Expand All @@ -171,7 +172,8 @@ private static async Task<int> Generate(string[] rest)
TypeMappings = args.All("--type-map"),
ModelFeatures = (args.Has("--clone") ? CSharpModelFeatures.Cloning : 0)
| (args.Has("--equality") ? CSharpModelFeatures.Equality : 0)
| (args.Has("--to-string") ? CSharpModelFeatures.StringRepresentation : 0)
| (args.Has("--to-string") ? CSharpModelFeatures.StringRepresentation : 0),
Serialization = args.Has("--serialization")
};

var resolver = DefaultImportResolver.Create(args.All("--import-dir"));
Expand Down
3 changes: 3 additions & 0 deletions Bond.Parser/CodeGeneration/CSharpGenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public sealed record CSharpGenerationOptions
public IReadOnlyList<string> TypeMappings { get; init; } = [];
public CSharpModelFeatures ModelFeatures { get; init; }

/// <summary>Generates Compact Binary serialization, which requires the BondTools.Runtime package.</summary>
public bool Serialization { get; init; }

public bool GenerateModelFeatures => ModelFeatures != CSharpModelFeatures.None;
public bool GenerateCloning => ModelFeatures.HasFlag(CSharpModelFeatures.Cloning);
public bool GenerateEquality => ModelFeatures.HasFlag(CSharpModelFeatures.Equality);
Expand Down
19 changes: 15 additions & 4 deletions Bond.Parser/CodeGeneration/CSharpGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,24 +133,29 @@ private void EmitStruct(StructDeclaration structure)
var callBaseConstructor = bases.Any(declaration =>
declaration.Fields.Any(field => IsMetaType(UnwrapAlias(field.Type, field.Location))));
var name = Identifier(structure.Name, structure.Location, typeName: true);
var self = QualifiedName(structure) + TypeArguments(structure.TypeParameters.Select(parameter =>
Identifier(parameter.Name, structure.Location, typeName: true)));

EmitStructDeclaration(structure, name);
EmitStructDeclaration(structure, name, self);
var initializers = EmitStructProperties(structure, inheritedNames);
EmitStructConstructors(structure, name, callBaseConstructor, initializers);

if (options.GenerateModelFeatures)
{
var self = QualifiedName(structure) + TypeArguments(structure.TypeParameters.Select(parameter =>
Identifier(parameter.Name, structure.Location, typeName: true)));
EmitModelMembers(structure, self);
}

if (options.Serialization)
{
EmitSerialization(structure, self);
}

Line(1, "}");
Line(0, "}");
Line(0);
}

private void EmitStructDeclaration(StructDeclaration structure, string name)
private void EmitStructDeclaration(StructDeclaration structure, string name, string self)
{
var parameters = TypeArguments(structure.TypeParameters.Select(parameter =>
Identifier(parameter.Name, structure.Location, typeName: true)));
Expand All @@ -161,6 +166,12 @@ private void EmitStructDeclaration(StructDeclaration structure, string name)
baseType += (baseType.Length == 0 ? " : " : ", ") + "global::System.ICloneable";
}

// A generic struct's serialization takes a codec for each type parameter, so it has no IBondStruct methods.
if (options.Serialization && structure.TypeParameters.Length == 0)
{
baseType += (baseType.Length == 0 ? " : " : ", ") + $"global::BondTools.Runtime.IBondStruct<{self}>";
}

BeginNamespace(structure);
EmitDocumentation(structure.LeadingTrivia, structure.TrailingTrivia, 1);
EmitAttributes(structure.Attributes, 1);
Expand Down
8 changes: 5 additions & 3 deletions Bond.Parser/CodeGeneration/CSharpMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ private bool TryMapAlias(AliasDeclaration alias, BondType[] arguments, SourceLoc
return true;
}

// Custom-mapped values are converted by a BondTypeAliasConverter class in their struct's C# namespace, as with gbc.
private string ConverterName(StructDeclaration owner, SourceLocation location) =>
"global::" + string.Join(".", CSharpNamespace(owner).Select(part => Identifier(part, location))) + ".BondTypeAliasConverter";

private string CustomDefaultValue(Field field, MappedType mapped, StructDeclaration owner)
{
var wireType = MapType(field.Type, field.Location, TypeMode.Wire);
Expand All @@ -170,9 +174,7 @@ private string CustomDefaultValue(Field field, MappedType mapped, StructDeclarat
"Keep its wire CLR type, or use a zero, empty, or nothing default.", field.Location);
}

var converter = "global::" + string.Join(".", CSharpNamespace(owner)
.Select(part => Identifier(part, field.Location))) + ".BondTypeAliasConverter";
return $"{converter}.Convert({value}, default({mapped.Name}))";
return $"{ConverterName(owner, field.Location)}.Convert({value}, default({mapped.Name}))";
}

private bool HasRuntimeCompatibleMappedDefault(Field field)
Expand Down
Loading
Loading