Skip to content

Commit 4b51cb2

Browse files
fix: ensure that types are always resolved (#2068)
* run a full resolveAll eagerly now that it's optimized * add caching to prevent excessive recursion on resolveAll * build ts file * minor 20% optimization to lookup * Cleanup to mark fields private * further 40% optimization to lookup that bails out early on fully-qualified types * lint fix * Include fields in global fullname cache for custom options
1 parent 69cced8 commit 4b51cb2

File tree

6 files changed

+76
-28
lines changed

6 files changed

+76
-28
lines changed

index.d.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,9 @@ export abstract class NamespaceBase extends ReflectionObject {
753753
/** Whether or not objects contained in this namespace need feature resolution. */
754754
protected _needsRecursiveFeatureResolution: boolean;
755755

756+
/** Whether or not objects contained in this namespace need a resolve. */
757+
protected _needsRecursiveResolve: boolean;
758+
756759
/** Nested objects of this namespace as an array for iteration. */
757760
public readonly nestedArray: ReflectionObject[];
758761

@@ -900,21 +903,6 @@ export abstract class ReflectionObject {
900903
/** Unique name within its namespace. */
901904
public name: string;
902905

903-
/** The edition specified for this object. Only relevant for top-level objects. */
904-
public _edition: string;
905-
906-
/**
907-
* The default edition to use for this object if none is specified. For legacy reasons,
908-
* this is proto2 except in the JSON parsing case where it was proto3.
909-
*/
910-
public _defaultEdition: string;
911-
912-
/** Resolved Features. */
913-
public _features: object;
914-
915-
/** Whether or not features have been resolved. */
916-
public _featuresResolved: boolean;
917-
918906
/** Parent namespace. */
919907
public parent: (Namespace|null);
920908

src/namespace.js

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ function Namespace(name, options) {
124124
* @protected
125125
*/
126126
this._needsRecursiveFeatureResolution = true;
127+
128+
/**
129+
* Whether or not objects contained in this namespace need a resolve.
130+
* @type {boolean}
131+
* @protected
132+
*/
133+
this._needsRecursiveResolve = true;
127134
}
128135

129136
function clearCache(namespace) {
@@ -273,11 +280,13 @@ Namespace.prototype.add = function add(object) {
273280
}
274281

275282
this._needsRecursiveFeatureResolution = true;
283+
this._needsRecursiveResolve = true;
276284

277285
// Also clear parent caches, since they need to recurse down.
278286
var parent = this;
279287
while(parent = parent.parent) {
280288
parent._needsRecursiveFeatureResolution = true;
289+
parent._needsRecursiveResolve = true;
281290
}
282291

283292
object.onAdd(this);
@@ -341,13 +350,16 @@ Namespace.prototype.define = function define(path, json) {
341350
* @returns {Namespace} `this`
342351
*/
343352
Namespace.prototype.resolveAll = function resolveAll() {
353+
if (!this._needsRecursiveResolve) return this;
354+
344355
var nested = this.nestedArray, i = 0;
345356
this.resolve();
346357
while (i < nested.length)
347358
if (nested[i] instanceof Namespace)
348359
nested[i++].resolveAll();
349360
else
350361
nested[i++].resolve();
362+
this._needsRecursiveResolve = false;
351363
return this;
352364
};
353365

@@ -389,29 +401,47 @@ Namespace.prototype.lookup = function lookup(path, filterTypes, parentAlreadyChe
389401
} else if (!path.length)
390402
return this;
391403

404+
var flatPath = path.join(".");
405+
392406
// Start at root if path is absolute
393407
if (path[0] === "")
394408
return this.root.lookup(path.slice(1), filterTypes);
395409

396-
var found = this._lookupImpl(path);
410+
// Early bailout for objects with matching absolute paths
411+
var found = this.root._fullyQualifiedObjects["." + flatPath];
412+
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
413+
return found;
414+
}
415+
416+
// Do a regular lookup at this namespace and below
417+
found = this._lookupImpl(path, flatPath);
397418
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
398419
return found;
399420
}
400421

401-
// If there hasn't been a match, try again at the parent
402-
if (this.parent === null || parentAlreadyChecked)
422+
if (parentAlreadyChecked)
403423
return null;
404-
return this.parent.lookup(path, filterTypes);
424+
425+
// If there hasn't been a match, walk up the tree and look more broadly
426+
var current = this;
427+
while (current.parent) {
428+
found = current.parent._lookupImpl(path, flatPath);
429+
if (found && (!filterTypes || filterTypes.indexOf(found.constructor) > -1)) {
430+
return found;
431+
}
432+
current = current.parent;
433+
}
434+
return null;
405435
};
406436

407437
/**
408438
* Internal helper for lookup that handles searching just at this namespace and below along with caching.
409439
* @param {string[]} path Path to look up
440+
* @param {string} flatPath Flattened version of the path to use as a cache key
410441
* @returns {ReflectionObject|null} Looked up object or `null` if none could be found
411442
* @private
412443
*/
413-
Namespace.prototype._lookupImpl = function lookup(path) {
414-
var flatPath = path.join(".");
444+
Namespace.prototype._lookupImpl = function lookup(path, flatPath) {
415445
if(Object.prototype.hasOwnProperty.call(this._lookupCache, flatPath)) {
416446
return this._lookupCache[flatPath];
417447
}
@@ -422,13 +452,15 @@ Namespace.prototype._lookupImpl = function lookup(path) {
422452
if (found) {
423453
if (path.length === 1) {
424454
exact = found;
425-
} else if (found instanceof Namespace && (found = found._lookupImpl(path.slice(1))))
426-
exact = found;
455+
} else if (found instanceof Namespace) {
456+
path = path.slice(1);
457+
exact = found._lookupImpl(path, path.join("."));
458+
}
427459

428460
// Otherwise try each nested namespace
429461
} else {
430462
for (var i = 0; i < this.nestedArray.length; ++i)
431-
if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i]._lookupImpl(path)))
463+
if (this._nestedArray[i] instanceof Namespace && (found = this._nestedArray[i]._lookupImpl(path, flatPath)))
432464
exact = found;
433465
}
434466

src/object.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,29 @@ function ReflectionObject(name, options) {
5151
/**
5252
* The edition specified for this object. Only relevant for top-level objects.
5353
* @type {string}
54+
* @private
5455
*/
5556
this._edition = null;
5657

5758
/**
5859
* The default edition to use for this object if none is specified. For legacy reasons,
5960
* this is proto2 except in the JSON parsing case where it was proto3.
6061
* @type {string}
62+
* @private
6163
*/
6264
this._defaultEdition = "proto2";
6365

6466
/**
6567
* Resolved Features.
6668
* @type {object}
69+
* @private
6770
*/
6871
this._features = {};
6972

7073
/**
7174
* Whether or not features have been resolved.
7275
* @type {boolean}
76+
* @private
7377
*/
7478
this._featuresResolved = false;
7579

src/root.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,19 @@ function Root(options) {
3636
*/
3737
this.files = [];
3838

39-
// Default to proto2 if unspecified.
39+
/**
40+
* Edition, defaults to proto2 if unspecified.
41+
* @type {string}
42+
* @private
43+
*/
4044
this._edition = "proto2";
45+
46+
/**
47+
* Global lookup cache of fully qualified names.
48+
* @type {Object.<string,ReflectionObject>}
49+
* @private
50+
*/
51+
this._fullyQualifiedObjects = {};
4152
}
4253

4354
/**
@@ -51,7 +62,7 @@ Root.fromJSON = function fromJSON(json, root) {
5162
root = new Root();
5263
if (json.options)
5364
root.setOptions(json.options);
54-
return root.addJSON(json.nested)._resolveFeaturesRecursive();
65+
return root.addJSON(json.nested).resolveAll();
5566
};
5667

5768
/**
@@ -100,7 +111,7 @@ Root.prototype.load = function load(filename, options, callback) {
100111
// Finishes loading by calling the callback (exactly once)
101112
function finish(err, root) {
102113
if (root) {
103-
root._resolveFeaturesRecursive();
114+
root.resolveAll();
104115
}
105116
/* istanbul ignore if */
106117
if (!callback) {
@@ -219,7 +230,7 @@ Root.prototype.load = function load(filename, options, callback) {
219230
if (resolved = self.resolvePath("", filename[i]))
220231
fetch(resolved);
221232
if (sync) {
222-
self._resolveFeaturesRecursive();
233+
self.resolveAll();
223234
return self;
224235
}
225236
if (!queued) {
@@ -268,6 +279,8 @@ Root.prototype.loadSync = function loadSync(filename, options) {
268279
* @override
269280
*/
270281
Root.prototype.resolveAll = function resolveAll() {
282+
if (!this._needsRecursiveResolve) return this;
283+
271284
if (this.deferred.length)
272285
throw Error("unresolvable extensions: " + this.deferred.map(function(field) {
273286
return "'extend " + field.extend + "' in " + field.parent.fullName;
@@ -335,6 +348,11 @@ Root.prototype._handleAdd = function _handleAdd(object) {
335348
object.parent[object.name] = object; // expose namespace as property of its parent
336349
}
337350

351+
if (object instanceof Type || object instanceof Enum || object instanceof Field) {
352+
// Only store types and enums for quick lookup during resolve.
353+
this._fullyQualifiedObjects[object.fullName] = object;
354+
}
355+
338356
// The above also adds uppercased (and thus conflict-free) nested types, services and enums as
339357
// properties of namespaces just like static code does. This allows using a .d.ts generated for
340358
// a static module with reflection-based solutions where the condition is met.
@@ -375,6 +393,8 @@ Root.prototype._handleRemove = function _handleRemove(object) {
375393
delete object.parent[object.name]; // unexpose namespaces
376394

377395
}
396+
397+
delete this._fullyQualifiedObjects[object.fullName];
378398
};
379399

380400
// Sets up cyclic dependencies (called in index-light)

src/service.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Service.prototype.get = function get(name) {
110110
* @override
111111
*/
112112
Service.prototype.resolveAll = function resolveAll() {
113+
if (!this._needsRecursiveResolve) return this;
114+
113115
Namespace.prototype.resolve.call(this);
114116
var methods = this.methodsArray;
115117
for (var i = 0; i < methods.length; ++i)

src/type.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ Type.prototype.toJSON = function toJSON(toJSONOptions) {
303303
* @override
304304
*/
305305
Type.prototype.resolveAll = function resolveAll() {
306+
if (!this._needsRecursiveResolve) return this;
307+
306308
Namespace.prototype.resolveAll.call(this);
307309
var oneofs = this.oneofsArray; i = 0;
308310
while (i < oneofs.length)

0 commit comments

Comments
 (0)