Skip to content

Fix module import statements and nested type fields in protoc-gen-d. #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -27,6 +27,11 @@ jobs:
dub test --compiler=$DC
dub build :protoc-gen-d --compiler=$DC

- name: Run Golden Tests
run: |
cd test
./run.sh

- name: Install automake
if: runner.os == 'macOS'
run: |
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.dub
build
dub.selections.json
test/generated
71 changes: 42 additions & 29 deletions protoc_gen_d/protoc-gen-d.d
Original file line number Diff line number Diff line change
@@ -69,17 +69,19 @@ class CodeGenerator

private void collectMessageAndEnumTypes(CodeGeneratorRequest request)
{
void collect(DescriptorProto messageType, string prefix)
void collect(DescriptorProto messageType, string prefix, string typePrefix)
{
auto absoluteName = prefix ~ "." ~ messageType.name;

if (absoluteName in collectedMessageTypes)
return;

collectedMessageTypes[absoluteName] = messageType;
auto type = typePrefix == "" ? messageType.name : typePrefix ~ "." ~ messageType.name;
typeFromDescriptor[messageType] = type;

foreach (nestedType; messageType.nestedTypes)
collect(nestedType, absoluteName);
collect(nestedType, absoluteName, type);

foreach (enumType; messageType.enumTypes)
collectedEnumTypes[absoluteName ~ "." ~ enumType.name] = enumType;
@@ -88,9 +90,10 @@ class CodeGenerator
foreach (file; request.protoFiles)
{
auto packagePrefix = file.package_ ? "." ~ file.package_ : "";
moduleFromFile[file.name] = moduleName(file);

foreach (messageType; file.messageTypes)
collect(messageType, packagePrefix);
collect(messageType, packagePrefix, "");

foreach (enumType; file.enumTypes)
collectedEnumTypes[packagePrefix ~ "." ~ enumType.name] = enumType;
@@ -126,7 +129,7 @@ class CodeGenerator
result ~= "import google.protobuf;\n";

foreach (dependency; fileDescriptor.dependencies)
result ~= "import %s;\n".format(dependency.moduleName);
result ~= "import %s;\n".format(moduleFromFile[dependency]);

if (!protocVersion.empty)
result ~= "\nenum protocVersion = %s;\n".format(protocVersion);
@@ -168,15 +171,16 @@ class CodeGenerator
{
if (field.oneofIndex < 0)
{
result ~= generateField(field, indent + indentSize);
result ~= generateField(field, indent + indentSize, messageType);
continue;
}

if (generatedOneofs.canFind(field.oneofIndex))
continue;

result ~= generateOneof(messageType.oneofDecls[field.oneofIndex],
messageType.fields.filter!(a => a.oneofIndex == field.oneofIndex).array, indent + indentSize);
messageType.fields.filter!(a => a.oneofIndex == field.oneofIndex).array, indent + indentSize,
messageType);
generatedOneofs ~= field.oneofIndex;
}

@@ -192,17 +196,18 @@ class CodeGenerator
return result.data;
}

private string generateField(FieldDescriptorProto field, size_t indent, bool printInitializer = true)
private string generateField(FieldDescriptorProto field, size_t indent, DescriptorProto parent)
{
import std.format : format;

return "%*s@Proto(%s) %s %s%s;\n".format(indent, "", fieldProtoFields(field), typeName(field),
field.name.underscoresToCamelCase(false), printInitializer ? fieldInitializer(field) : "");
auto type = typeName(field, parent);
return "%*s@Proto(%s) %s %s%s;\n".format(indent, "", fieldProtoFields(field), type,
field.name.underscoresToCamelCase(false), fieldInitializer(type));
}

private string generateOneof(OneofDescriptorProto oneof, FieldDescriptorProto[] fields, size_t indent)
private string generateOneof(OneofDescriptorProto oneof, FieldDescriptorProto[] fields, size_t indent, DescriptorProto parent)
{
return generateOneofCaseEnum(oneof, fields, indent) ~ generateOneofUnion(oneof, fields, indent);
return generateOneofCaseEnum(oneof, fields, indent) ~ generateOneofUnion(oneof, fields, indent, parent);
}

private string generateOneofCaseEnum(OneofDescriptorProto oneof, FieldDescriptorProto[] fields, size_t indent)
@@ -228,7 +233,7 @@ class CodeGenerator
return result.data;
}

private string generateOneofUnion(OneofDescriptorProto oneof, FieldDescriptorProto[] fields, size_t indent)
private string generateOneofUnion(OneofDescriptorProto oneof, FieldDescriptorProto[] fields, size_t indent, DescriptorProto parent)
{
import std.format : format;
import std.array : appender;
@@ -237,19 +242,20 @@ class CodeGenerator
result ~= "%*s@Oneof(\"_%sCase\") union\n".format(indent, "", oneof.name.underscoresToCamelCase(false));
result ~= "%*s{\n".format(indent, "");
foreach (field; fields)
result ~= generateOneofField(field, indent + indentSize, field == fields[0]);
result ~= generateOneofField(field, indent + indentSize, field == fields[0], parent);
result ~= "%*s}\n".format(indent, "");

return result.data;
}

private string generateOneofField(FieldDescriptorProto field, size_t indent, bool printInitializer)
private string generateOneofField(FieldDescriptorProto field, size_t indent, bool printInitializer, DescriptorProto parent)
{
import std.format : format;

auto type = typeName(field, parent);
return "%*s@Proto(%s) %s _%5$s%6$s; mixin(oneofAccessors!_%5$s);\n".format(indent, "", fieldProtoFields(field),
typeName(field), field.name.underscoresToCamelCase(false),
printInitializer ? fieldInitializer(field) : "");
type, field.name.underscoresToCamelCase(false),
printInitializer ? fieldInitializer(type) : "");
}

private string generateEnum(EnumDescriptorProto enumType, size_t indent = 0)
@@ -337,7 +343,7 @@ class CodeGenerator
auto fieldMessageType = messageType(field);
enforce!CodeGeneratorException(fieldMessageType !is null, "Field '" ~ field.name ~
"' has unknown message type " ~ field.typeName ~ "`");
return fieldMessageType.name;
return typeFromDescriptor[fieldMessageType];
}
case TYPE_ENUM:
{
@@ -397,6 +403,20 @@ class CodeGenerator
return fieldBaseTypeName;
}

string typeName(FieldDescriptorProto field, DescriptorProto parent)
{
import std.algorithm : startsWith;

auto type = typeName(field);

// Remove parent type prefix.
auto parentType = typeFromDescriptor[parent];
if (type.startsWith(parentType ~ ".")) {
return type[parentType.length + 1 .. $];
}
return type;
}

private string fieldProtoFields(FieldDescriptorProto field)
{
import std.algorithm : stripRight;
@@ -420,13 +440,12 @@ class CodeGenerator
.join(", ");
}

private string fieldInitializer(FieldDescriptorProto field)
private string fieldInitializer(string fieldTypeName)
{
import std.algorithm : endsWith;
import std.algorithm : canFind, endsWith;
import std.format : format;

auto fieldTypeName = typeName(field);
if (fieldTypeName.endsWith("]"))
if (fieldTypeName.endsWith("]") || fieldTypeName.canFind('.'))
return " = protoDefaultValue!(%s)".format(fieldTypeName);
else
return " = protoDefaultValue!%s".format(fieldTypeName);
@@ -435,6 +454,8 @@ class CodeGenerator
private string protocVersion;
private DescriptorProto[string] collectedMessageTypes;
private EnumDescriptorProto[string] collectedEnumTypes;
private string[string] moduleFromFile;
private string[DescriptorProto] typeFromDescriptor;
}

private FieldDescriptorProto fieldByNumber(DescriptorProto messageType, int fieldNumber)
@@ -520,14 +541,6 @@ private string moduleName(FileDescriptorProto fileDescriptor)
return moduleName.escapeKeywords;
}

private string moduleName(string fileName)
{
import std.array : replace;
import std.string : chomp;

return fileName.chomp(".proto").replace("/", ".").escapeKeywords;
}

private string underscoresToCamelCase(string input, bool capitalizeNextLetter)
{
import std.array : appender;
3 changes: 2 additions & 1 deletion src/google/protobuf/common.d
Original file line number Diff line number Diff line change
@@ -130,13 +130,14 @@ enum string oneofAccessorName(alias field) = {

enum string oneofAccessors(alias field) = {
import std.string : format;
import std.traits : fullyQualifiedName;

enum accessorName = oneofAccessorName!field;

return "
@property %1$s %2$s() { return %3$s == typeof(%3$s).%2$s ? _%2$s : protoDefaultValue!(%1$s); }
@property void %2$s(%1$s _) { _%2$s = _; %3$s = typeof(%3$s).%2$s; }
".format(typeof(field).stringof, accessorName, oneofCaseFieldName!field);
".format(fullyQualifiedName!(typeof(field)), accessorName, oneofCaseFieldName!field);
}();

template Message(T)
1 change: 1 addition & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-test-library
18 changes: 18 additions & 0 deletions test/a/b/c.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Test case for nested module and type (#33).
syntax = "proto3";

package a;

message A {
message B {
int32 i = 1;
}
}

message C {
A.B b = 1;
oneof o {
A.B ob = 2;
int32 oi = 3;
}
}
10 changes: 10 additions & 0 deletions test/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "test",
"targetType": "library",
"targetName": "test",
"importPaths": ["golden"],
"sourcePaths": ["golden"],
"dependencies": {
"protobuf": { "path": ".." }
}
}
36 changes: 36 additions & 0 deletions test/golden/a/c.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: a/b/c.proto

module a.c;

import google.protobuf;

enum protocVersion = 3012004;

class A
{

static class B
{
@Proto(1) int i = protoDefaultValue!int;
}
}

class C
{
@Proto(1) A.B b = protoDefaultValue!(A.B);
enum OCase
{
oNotSet = 0,
ob = 2,
oi = 3,
}
OCase _oCase = OCase.oNotSet;
@property OCase oCase() { return _oCase; }
void clearO() { _oCase = OCase.oNotSet; }
@Oneof("_oCase") union
{
@Proto(2) A.B _ob = protoDefaultValue!(A.B); mixin(oneofAccessors!_ob);
@Proto(3) int _oi; mixin(oneofAccessors!_oi);
}
}
File renamed without changes.
31 changes: 31 additions & 0 deletions test/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
# Test script for protoc-gen-d comparing generated/*.d with golden/*.d.
set -u

cd ..
dub build :protoc-gen-d
cd -

rm -rf generated
mkdir generated

dub test
if [ $? -ne 0 ]; then
echo "ERROR: golden is invalid."
exit 1
fi

check() {
protoc ${PROTO_PATH:-} --plugin=../build/protoc-gen-d "${1}" --d_out=generated
# Ignore `enum protocVersion = ...;" line because it depends on the env.
diff -I '^enum protocVersion = .*;$' generated/${2} golden/${2}
if [ $? -ne 0 ]; then
echo "ERROR: generated/${2} is different from golden/${2}."
exit 1
fi
}

check a/b/c.proto a/c.d
check generated_code.proto generated_code.d

echo "SUCCESS: all test cases finished."