Skip to content

Commit 785416f

Browse files
Hook up repeated field encoding feature
1 parent f2ccb99 commit 785416f

File tree

5 files changed

+162
-51
lines changed

5 files changed

+162
-51
lines changed

src/field.js

+21-22
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,6 @@ function Field(name, id, type, rule, extend, options, comment) {
183183
*/
184184
this.declaringField = null;
185185

186-
/**
187-
* Internally remembers whether this field is packed.
188-
* @type {boolean|null}
189-
* @private
190-
*/
191-
this._packed = null;
192-
193186
/**
194187
* Comment for this field.
195188
* @type {string|null}
@@ -198,26 +191,21 @@ function Field(name, id, type, rule, extend, options, comment) {
198191
}
199192

200193
/**
201-
* Determines whether this field is packed. Only relevant when repeated and working with proto2.
194+
* Determines whether this field is packed. Only relevant when repeated.
202195
* @name Field#packed
203196
* @type {boolean}
204197
* @readonly
205198
*/
206199
Object.defineProperty(Field.prototype, "packed", {
207200
get: function() {
208-
// defaults to packed=true if not explicity set to false
209-
if (this._packed === null)
210-
this._packed = this.getOption("packed") !== false;
211-
return this._packed;
201+
return this._features.repeated_field_encoding === "PACKED";
212202
}
213203
});
214204

215205
/**
216206
* @override
217207
*/
218208
Field.prototype.setOption = function setOption(name, value, ifNotSet) {
219-
if (name === "packed") // clear cached before setting
220-
this._packed = null;
221209
return ReflectionObject.prototype.setOption.call(this, name, value, ifNotSet);
222210
};
223211

@@ -284,7 +272,7 @@ Field.prototype.resolve = function resolve() {
284272

285273
// remove unnecessary options
286274
if (this.options) {
287-
if (this.options.packed === true || this.options.packed !== undefined && this.resolvedType && !(this.resolvedType instanceof Enum))
275+
if (this.options.packed !== undefined && this.resolvedType && !(this.resolvedType instanceof Enum))
288276
delete this.options.packed;
289277
if (!Object.keys(this.options).length)
290278
this.options = undefined;
@@ -319,16 +307,27 @@ Field.prototype.resolve = function resolve() {
319307
if (this.parent instanceof Type)
320308
this.parent.ctor.prototype[this.name] = this.defaultValue;
321309

322-
323-
if (this.parent) {
324-
var parentFeaturesCopy = Object.assign({}, this.parent._features);
325-
this._features = Object.assign(parentFeaturesCopy, this._protoFeatures || {});
326-
} else {
327-
this._features = Object.assign({}, this._protoFeatures);
328-
}
329310
return ReflectionObject.prototype.resolve.call(this);
330311
};
331312

313+
/**
314+
* Infers field features from legacy syntax that may have been specified differently.
315+
* in older editions.
316+
* @param {string|undefined} edition The edition this proto is on, or undefined if pre-editions
317+
* @returns {object} The feature values to override
318+
*/
319+
Field.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(edition) {
320+
if (edition) return {};
321+
322+
var features = {};
323+
if (this.getOption("packed") === true) {
324+
features.repeated_field_encoding = "PACKED";
325+
} else if (this.getOption("packed") === false) {
326+
features.repeated_field_encoding = "EXPANDED";
327+
}
328+
return features;
329+
}
330+
332331
/**
333332
* Decorator function as returned by {@link Field.d} and {@link MapField.d} (TypeScript).
334333
* @typedef FieldDecorator

src/object.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -177,31 +177,44 @@ ReflectionObject.prototype.resolve = function resolve() {
177177
ReflectionObject.prototype._resolveFeatures = function _resolveFeatures() {
178178
var defaults = {};
179179

180+
var edition = this.root.getOption("edition");
180181
if (this instanceof Root) {
181182
if (this.root.getOption("syntax") === "proto3") {
182183
defaults = Object.assign({}, proto3Defaults);
183-
} else if (this.root.getOption("edition") === "2023") {
184+
} else if (edition === "2023") {
184185
defaults = Object.assign({}, editions2023Defaults);
185186
} else {
186187
defaults = Object.assign({}, proto2Defaults);
187188
}
188189
}
189190

191+
var protoFeatures = Object.assign(Object.assign({}, this._protoFeatures), this._inferLegacyProtoFeatures(edition));
192+
190193
if (this instanceof Root) {
191-
this._features = Object.assign(defaults, this._protoFeatures || {});
194+
this._features = Object.assign(defaults, protoFeatures || {});
192195
// fields in Oneofs aren't actually children of them, so we have to
193196
// special-case it
194197
} else if (this.partOf instanceof OneOf) {
195198
var lexicalParentFeaturesCopy = Object.assign({}, this.partOf._features);
196-
this._features = Object.assign(lexicalParentFeaturesCopy, this._protoFeatures || {});
199+
this._features = Object.assign(lexicalParentFeaturesCopy, protoFeatures || {});
197200
} else if (this.parent) {
198201
var parentFeaturesCopy = Object.assign({}, this.parent._features);
199-
this._features = Object.assign(parentFeaturesCopy, this._protoFeatures || {});
202+
this._features = Object.assign(parentFeaturesCopy, protoFeatures || {});
200203
} else {
201-
this._features = Object.assign({}, this._protoFeatures);
204+
this._features = Object.assign({}, protoFeatures);
202205
}
203206
};
204207

208+
/**
209+
* Infers features from legacy syntax that may have been specified differently.
210+
* in older editions.
211+
* @param {string|undefined} edition The edition this proto is on, or undefined if pre-editions
212+
* @returns {object} The feature values to override
213+
*/
214+
ReflectionObject.prototype._inferLegacyProtoFeatures = function _inferLegacyProtoFeatures(edition) {
215+
return {};
216+
}
217+
205218
/**
206219
* Gets an option value.
207220
* @param {string} name Option name

src/parse.js

-6
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,6 @@ function parse(source, root, options) {
460460
} else {
461461
parent.add(field);
462462
}
463-
464-
// JSON defaults to packed=true if not set so we have to set packed=false explicity when
465-
// parsing proto2 descriptors without the option, where applicable. This must be done for
466-
// all known packable types and anything that could be an enum (= is not a basic type).
467-
if (!isProto3 && field.repeated && (types.packed[type] !== undefined || types.basic[type] === undefined))
468-
field.setOption("packed", false, /* ifNotSet */ true);
469463
}
470464

471465
function parseGroup(parent, rule) {

tests/comp_packed-repeated.js

+74-6
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,96 @@ var tape = require("tape");
22

33
var protobuf = require("..");
44

5-
var proto1 = "message Test {\
5+
var packedOption = "message Test {\
66
repeated uint32 a = 1 [packed = true];\
77
}";
88

9-
var proto2 = "message Test {\
9+
var unpackedOption = "message Test {\
1010
repeated uint32 a = 1 [packed = false];\
1111
}";
1212

13+
var packedFeature = "edition = \"2023\";\
14+
message Test {\
15+
repeated uint32 a = 1 [features.repeated_field_encoding = PACKED];\
16+
}";
17+
18+
var unpackedFeature = "edition = \"2023\";\
19+
message Test {\
20+
repeated uint32 a = 1 [features.repeated_field_encoding = EXPANDED];\
21+
}";
22+
23+
var regular = "message Test {\
24+
repeated uint32 a = 1;\
25+
}";
26+
1327
var msg = {
1428
a: [1,2,3]
1529
};
1630

17-
tape.test("packed repeated values", function(test) {
18-
var root1 = protobuf.parse(proto1).root,
19-
root2 = protobuf.parse(proto2).root;
31+
tape.test("packed repeated values roundtrip", function(test) {
32+
var root1 = protobuf.parse(packedOption).root.resolveAll(),
33+
root2 = protobuf.parse(unpackedOption).root.resolveAll();
2034
var Test1 = root1.lookup("Test"),
2135
Test2 = root2.lookup("Test");
22-
36+
2337
var dec1 = Test2.decode(Test1.encode(msg).finish());
2438
test.same(dec1, msg, "should decode packed even if defined non-packed");
2539
var dec2 = Test1.decode(Test2.encode(msg).finish());
2640
test.same(dec2, msg, "should decode non-packed even if defined packed");
2741

2842
test.end();
2943
});
44+
45+
tape.test("packed repeated values encode", function(top) {
46+
[
47+
packedOption,
48+
`syntax = "proto3";\n` + regular,
49+
`syntax = "proto3";\n` + packedOption,
50+
`edition = "2023";\n` + regular,
51+
packedFeature,
52+
].forEach(function(proto, index) {
53+
tape.test("proto " + index, function(test) {
54+
var root = protobuf.parse(proto).root.resolveAll();
55+
var Test = root.lookup("Test");
56+
57+
var buf = Test.encode(msg).finish();
58+
test.equal(buf.length, 5, "a total of 4 bytes");
59+
test.equal(buf[0], 1 << 3 | 2, "id 1, wireType 2");
60+
test.equal(buf[1], 3, "length 3");
61+
test.equal(buf[2], 1, "element 1");
62+
test.equal(buf[3], 2, "element 2");
63+
test.equal(buf[4], 3, "element 3");
64+
65+
test.end();
66+
});
67+
});
68+
69+
top.end()
70+
});
71+
72+
tape.test("packed repeated values encode", function(top) {
73+
[
74+
unpackedOption,
75+
regular,
76+
`syntax = "proto3";\n` + unpackedOption,
77+
unpackedFeature,
78+
].forEach(function(proto, index) {
79+
tape.test("proto " + index, function(test) {
80+
var root = protobuf.parse(proto).root.resolveAll();
81+
var Test = root.lookup("Test");
82+
83+
var buf = Test.encode(msg).finish();
84+
test.equal(buf.length, 6, "a total of 6 bytes");
85+
test.equal(buf[0], 1 << 3 | 0, "id 1, wireType 0");
86+
test.equal(buf[1], 1, "element 1");
87+
test.equal(buf[2], 1 << 3 | 0, "id 1, wireType 0");
88+
test.equal(buf[3], 2, "element 2");
89+
test.equal(buf[4], 1 << 3 | 0, "id 1, wireType 0");
90+
test.equal(buf[5], 3, "element 3");
91+
92+
test.end()
93+
});
94+
});
95+
96+
top.end()
97+
});

tests/feature_resolution_editions.js

+49-12
Original file line numberDiff line numberDiff line change
@@ -423,16 +423,53 @@ tape.test("feature resolution editions precedence", function(test) {
423423
if (err)
424424
throw test.fail(err.message);
425425

426-
test.same(root.lookup("Message").lookupEnum("SomeEnumInMessage")._features,
427-
{
428-
enum_type: 'CLOSED',
429-
field_presence: 'EXPLICIT',
430-
json_format: 'LEGACY_BEST_EFFORT',
431-
message_encoding: 'LENGTH_PREFIXED',
432-
repeated_field_encoding: 'EXPANDED',
433-
utf8_validation: 'NONE',
434-
amazing_feature: 'G'
435-
})
436-
test.end();
426+
test.same(root.lookup("Message").lookupEnum("SomeEnumInMessage")._features,
427+
{
428+
enum_type: 'CLOSED',
429+
field_presence: 'EXPLICIT',
430+
json_format: 'LEGACY_BEST_EFFORT',
431+
message_encoding: 'LENGTH_PREFIXED',
432+
repeated_field_encoding: 'EXPANDED',
433+
utf8_validation: 'NONE',
434+
amazing_feature: 'G'
435+
})
436+
test.end();
437437
});
438-
})
438+
});
439+
440+
tape.test("feature resolution inferred proto2 repeated encoding", function(test) {
441+
var root = protobuf.parse(`syntax = "proto2";
442+
message Message {
443+
repeated int32 default = 1;
444+
repeated int32 packed = 2 [packed = true];
445+
repeated int32 unpacked = 3 [packed = false];
446+
}`).root.resolveAll();
447+
448+
test.notOk(root.lookup("Message").fields.default.packed)
449+
test.equal(root.lookup("Message").fields.default._features.repeated_field_encoding, "EXPANDED")
450+
test.ok(root.lookup("Message").fields.packed.packed)
451+
test.equal(root.lookup("Message").fields.packed._features.repeated_field_encoding, "PACKED")
452+
test.notOk(root.lookup("Message").fields.unpacked.packed)
453+
test.equal(root.lookup("Message").fields.unpacked._features.repeated_field_encoding, "EXPANDED")
454+
455+
test.end();
456+
});
457+
458+
tape.test("feature resolution inferred proto3 repeated encoding", function(test) {
459+
var root = protobuf.parse(`syntax = "proto3";
460+
message Message {
461+
repeated int32 default = 1;
462+
repeated int32 packed = 2 [packed = true];
463+
repeated int32 unpacked = 3 [packed = false];
464+
}`).root.resolveAll();
465+
466+
test.ok(root.lookup("Message").fields.default.packed)
467+
test.equal(root.lookup("Message").fields.default._features.repeated_field_encoding, "PACKED")
468+
test.ok(root.lookup("Message").fields.packed.packed)
469+
test.equal(root.lookup("Message").fields.packed._features.repeated_field_encoding, "PACKED")
470+
test.notOk(root.lookup("Message").fields.unpacked.packed)
471+
test.equal(root.lookup("Message").fields.unpacked._features.repeated_field_encoding, "EXPANDED")
472+
473+
test.end();
474+
});
475+

0 commit comments

Comments
 (0)