diff --git a/packages/jsii-calc/lib/compliance.ts b/packages/jsii-calc/lib/compliance.ts
index 3d2dcaed07..b90576a6fc 100644
--- a/packages/jsii-calc/lib/compliance.ts
+++ b/packages/jsii-calc/lib/compliance.ts
@@ -926,3 +926,16 @@ export class ClassWithPrivateConstructorAndAutomaticProperties implements IInter
private constructor(public readonly readOnlyString: string, public readWriteString: string) {
}
}
+
+export interface IInterfaceWithMethods {
+ readonly value: string;
+ doThings(): void;
+}
+
+/**
+ * Even though this interface has only properties, it is disqualified from being a datatype
+ * because it inherits from an interface that is not a datatype.
+ */
+export interface IInterfaceThatShouldNotBeADataType extends IInterfaceWithMethods {
+ readonly otherValue: string;
+}
\ No newline at end of file
diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii
index d89e2d843d..cca58d3622 100644
--- a/packages/jsii-calc/test/assembly.jsii
+++ b/packages/jsii-calc/test/assembly.jsii
@@ -1452,6 +1452,52 @@
"kind": "interface",
"name": "IFriendlyRandomGenerator"
},
+ "jsii-calc.IInterfaceThatShouldNotBeADataType": {
+ "assembly": "jsii-calc",
+ "docs": {
+ "comment": "Even though this interface has only properties, it is disqualified from being a datatype\nbecause it inherits from an interface that is not a datatype."
+ },
+ "fqn": "jsii-calc.IInterfaceThatShouldNotBeADataType",
+ "interfaces": [
+ {
+ "fqn": "jsii-calc.IInterfaceWithMethods"
+ }
+ ],
+ "kind": "interface",
+ "name": "IInterfaceThatShouldNotBeADataType",
+ "properties": [
+ {
+ "abstract": true,
+ "immutable": true,
+ "name": "otherValue",
+ "type": {
+ "primitive": "string"
+ }
+ }
+ ]
+ },
+ "jsii-calc.IInterfaceWithMethods": {
+ "assembly": "jsii-calc",
+ "fqn": "jsii-calc.IInterfaceWithMethods",
+ "kind": "interface",
+ "methods": [
+ {
+ "abstract": true,
+ "name": "doThings"
+ }
+ ],
+ "name": "IInterfaceWithMethods",
+ "properties": [
+ {
+ "abstract": true,
+ "immutable": true,
+ "name": "value",
+ "type": {
+ "primitive": "string"
+ }
+ }
+ ]
+ },
"jsii-calc.IInterfaceWithProperties": {
"assembly": "jsii-calc",
"datatype": true,
@@ -3355,5 +3401,5 @@
}
},
"version": "0.7.7",
- "fingerprint": "16f4wL/B1M7rOOzyAzBEtqlOi2GYhDAU5rctonoha5Y="
+ "fingerprint": "vJH1gHlpRxKo77e0kE+6KATwgsZB0VpBcFEo/9OIG7Q="
}
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii
index d89e2d843d..cca58d3622 100644
--- a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii
@@ -1452,6 +1452,52 @@
"kind": "interface",
"name": "IFriendlyRandomGenerator"
},
+ "jsii-calc.IInterfaceThatShouldNotBeADataType": {
+ "assembly": "jsii-calc",
+ "docs": {
+ "comment": "Even though this interface has only properties, it is disqualified from being a datatype\nbecause it inherits from an interface that is not a datatype."
+ },
+ "fqn": "jsii-calc.IInterfaceThatShouldNotBeADataType",
+ "interfaces": [
+ {
+ "fqn": "jsii-calc.IInterfaceWithMethods"
+ }
+ ],
+ "kind": "interface",
+ "name": "IInterfaceThatShouldNotBeADataType",
+ "properties": [
+ {
+ "abstract": true,
+ "immutable": true,
+ "name": "otherValue",
+ "type": {
+ "primitive": "string"
+ }
+ }
+ ]
+ },
+ "jsii-calc.IInterfaceWithMethods": {
+ "assembly": "jsii-calc",
+ "fqn": "jsii-calc.IInterfaceWithMethods",
+ "kind": "interface",
+ "methods": [
+ {
+ "abstract": true,
+ "name": "doThings"
+ }
+ ],
+ "name": "IInterfaceWithMethods",
+ "properties": [
+ {
+ "abstract": true,
+ "immutable": true,
+ "name": "value",
+ "type": {
+ "primitive": "string"
+ }
+ }
+ ]
+ },
"jsii-calc.IInterfaceWithProperties": {
"assembly": "jsii-calc",
"datatype": true,
@@ -3355,5 +3401,5 @@
}
},
"version": "0.7.7",
- "fingerprint": "16f4wL/B1M7rOOzyAzBEtqlOi2GYhDAU5rctonoha5Y="
+ "fingerprint": "vJH1gHlpRxKo77e0kE+6KATwgsZB0VpBcFEo/9OIG7Q="
}
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceThatShouldNotBeADataType.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceThatShouldNotBeADataType.cs
new file mode 100644
index 0000000000..f5bc2e64d9
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceThatShouldNotBeADataType.cs
@@ -0,0 +1,18 @@
+using Amazon.JSII.Runtime.Deputy;
+
+namespace Amazon.JSII.Tests.CalculatorNamespace
+{
+ ///
+ /// Even though this interface has only properties, it is disqualified from being a datatype
+ /// because it inherits from an interface that is not a datatype.
+ ///
+ [JsiiInterface(typeof(IIInterfaceThatShouldNotBeADataType), "jsii-calc.IInterfaceThatShouldNotBeADataType")]
+ public interface IIInterfaceThatShouldNotBeADataType : IIInterfaceWithMethods
+ {
+ [JsiiProperty("otherValue", "{\"primitive\":\"string\"}")]
+ string OtherValue
+ {
+ get;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceWithMethods.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceWithMethods.cs
new file mode 100644
index 0000000000..ccd793c1da
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IIInterfaceWithMethods.cs
@@ -0,0 +1,17 @@
+using Amazon.JSII.Runtime.Deputy;
+
+namespace Amazon.JSII.Tests.CalculatorNamespace
+{
+ [JsiiInterface(typeof(IIInterfaceWithMethods), "jsii-calc.IInterfaceWithMethods")]
+ public interface IIInterfaceWithMethods
+ {
+ [JsiiProperty("value", "{\"primitive\":\"string\"}")]
+ string Value
+ {
+ get;
+ }
+
+ [JsiiMethod("doThings", null, "[]")]
+ void DoThings();
+ }
+}
\ No newline at end of file
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceThatShouldNotBeADataTypeProxy.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceThatShouldNotBeADataTypeProxy.cs
new file mode 100644
index 0000000000..2ee8dfa35d
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceThatShouldNotBeADataTypeProxy.cs
@@ -0,0 +1,34 @@
+using Amazon.JSII.Runtime.Deputy;
+
+namespace Amazon.JSII.Tests.CalculatorNamespace
+{
+ ///
+ /// Even though this interface has only properties, it is disqualified from being a datatype
+ /// because it inherits from an interface that is not a datatype.
+ ///
+ [JsiiTypeProxy(typeof(IIInterfaceThatShouldNotBeADataType), "jsii-calc.IInterfaceThatShouldNotBeADataType")]
+ internal sealed class IInterfaceThatShouldNotBeADataTypeProxy : DeputyBase, IIInterfaceThatShouldNotBeADataType
+ {
+ private IInterfaceThatShouldNotBeADataTypeProxy(ByRefValue reference): base(reference)
+ {
+ }
+
+ [JsiiProperty("otherValue", "{\"primitive\":\"string\"}")]
+ public string OtherValue
+ {
+ get => GetInstanceProperty();
+ }
+
+ [JsiiProperty("value", "{\"primitive\":\"string\"}")]
+ public string Value
+ {
+ get => GetInstanceProperty();
+ }
+
+ [JsiiMethod("doThings", null, "[]")]
+ public void DoThings()
+ {
+ InvokeInstanceVoidMethod(new object[]{});
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceWithMethodsProxy.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceWithMethodsProxy.cs
new file mode 100644
index 0000000000..1c4c8947e2
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/IInterfaceWithMethodsProxy.cs
@@ -0,0 +1,24 @@
+using Amazon.JSII.Runtime.Deputy;
+
+namespace Amazon.JSII.Tests.CalculatorNamespace
+{
+ [JsiiTypeProxy(typeof(IIInterfaceWithMethods), "jsii-calc.IInterfaceWithMethods")]
+ internal sealed class IInterfaceWithMethodsProxy : DeputyBase, IIInterfaceWithMethods
+ {
+ private IInterfaceWithMethodsProxy(ByRefValue reference): base(reference)
+ {
+ }
+
+ [JsiiProperty("value", "{\"primitive\":\"string\"}")]
+ public string Value
+ {
+ get => GetInstanceProperty();
+ }
+
+ [JsiiMethod("doThings", null, "[]")]
+ public void DoThings()
+ {
+ InvokeInstanceVoidMethod(new object[]{});
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java
index 6f2ff3c467..aec0d95abc 100644
--- a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/$Module.java
@@ -40,6 +40,8 @@ protected Class> resolveClass(final String fqn) throws ClassNotFoundException
case "jsii-calc.GiveMeStructs": return software.amazon.jsii.tests.calculator.GiveMeStructs.class;
case "jsii-calc.IFriendlier": return software.amazon.jsii.tests.calculator.IFriendlier.class;
case "jsii-calc.IFriendlyRandomGenerator": return software.amazon.jsii.tests.calculator.IFriendlyRandomGenerator.class;
+ case "jsii-calc.IInterfaceThatShouldNotBeADataType": return software.amazon.jsii.tests.calculator.IInterfaceThatShouldNotBeADataType.class;
+ case "jsii-calc.IInterfaceWithMethods": return software.amazon.jsii.tests.calculator.IInterfaceWithMethods.class;
case "jsii-calc.IInterfaceWithProperties": return software.amazon.jsii.tests.calculator.IInterfaceWithProperties.class;
case "jsii-calc.IInterfaceWithPropertiesExtension": return software.amazon.jsii.tests.calculator.IInterfaceWithPropertiesExtension.class;
case "jsii-calc.IRandomNumberGenerator": return software.amazon.jsii.tests.calculator.IRandomNumberGenerator.class;
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceThatShouldNotBeADataType.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceThatShouldNotBeADataType.java
new file mode 100644
index 0000000000..ce0b151763
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceThatShouldNotBeADataType.java
@@ -0,0 +1,34 @@
+package software.amazon.jsii.tests.calculator;
+
+/**
+ * Even though this interface has only properties, it is disqualified from being a datatype
+ * because it inherits from an interface that is not a datatype.
+ */
+@javax.annotation.Generated(value = "jsii-pacmak")
+public interface IInterfaceThatShouldNotBeADataType extends software.amazon.jsii.JsiiSerializable, software.amazon.jsii.tests.calculator.IInterfaceWithMethods {
+ java.lang.String getOtherValue();
+
+ /**
+ * A proxy class which represents a concrete javascript instance of this type.
+ */
+ final static class Jsii$Proxy extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.IInterfaceThatShouldNotBeADataType {
+ protected Jsii$Proxy(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
+ super(mode);
+ }
+
+ @Override
+ public java.lang.String getOtherValue() {
+ return this.jsiiGet("otherValue", java.lang.String.class);
+ }
+
+ @Override
+ public java.lang.String getValue() {
+ return this.jsiiGet("value", java.lang.String.class);
+ }
+
+ @Override
+ public void doThings() {
+ this.jsiiCall("doThings", Void.class);
+ }
+ }
+}
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceWithMethods.java b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceWithMethods.java
new file mode 100644
index 0000000000..2dba1ed12b
--- /dev/null
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/src/main/java/software/amazon/jsii/tests/calculator/IInterfaceWithMethods.java
@@ -0,0 +1,26 @@
+package software.amazon.jsii.tests.calculator;
+
+@javax.annotation.Generated(value = "jsii-pacmak")
+public interface IInterfaceWithMethods extends software.amazon.jsii.JsiiSerializable {
+ java.lang.String getValue();
+ void doThings();
+
+ /**
+ * A proxy class which represents a concrete javascript instance of this type.
+ */
+ final static class Jsii$Proxy extends software.amazon.jsii.JsiiObject implements software.amazon.jsii.tests.calculator.IInterfaceWithMethods {
+ protected Jsii$Proxy(final software.amazon.jsii.JsiiObject.InitializationMode mode) {
+ super(mode);
+ }
+
+ @Override
+ public java.lang.String getValue() {
+ return this.jsiiGet("value", java.lang.String.class);
+ }
+
+ @Override
+ public void doThings() {
+ this.jsiiCall("doThings", Void.class);
+ }
+ }
+}
diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst b/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst
index 92792836ca..df7f522b5f 100644
--- a/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst
+++ b/packages/jsii-pacmak/test/expected.jsii-calc/sphinx/jsii-calc.rst
@@ -1556,6 +1556,97 @@ IFriendlyRandomGenerator (interface)
:abstract: Yes
+IInterfaceThatShouldNotBeADataType (interface)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:class:: IInterfaceThatShouldNotBeADataType
+
+ **Language-specific names:**
+
+ .. tabs::
+
+ .. code-tab:: c#
+
+ using Amazon.JSII.Tests.CalculatorNamespace;
+
+ .. code-tab:: java
+
+ import software.amazon.jsii.tests.calculator.IInterfaceThatShouldNotBeADataType;
+
+ .. code-tab:: javascript
+
+ // IInterfaceThatShouldNotBeADataType is an interface
+
+ .. code-tab:: typescript
+
+ import { IInterfaceThatShouldNotBeADataType } from 'jsii-calc';
+
+
+
+ Even though this interface has only properties, it is disqualified from being a datatype because it inherits from an interface that is not a datatype.
+
+
+ :extends: :py:class:`~jsii-calc.IInterfaceWithMethods`\
+
+
+ .. py:attribute:: otherValue
+
+ :type: string *(readonly)* *(abstract)*
+
+
+ .. py:method:: doThings()
+
+ *Inherited from* :py:meth:`jsii-calc.IInterfaceWithMethods `
+
+ :abstract: Yes
+
+
+ .. py:attribute:: value
+
+ *Inherited from* :py:attr:`jsii-calc.IInterfaceWithMethods `
+
+ :type: string *(readonly)* *(abstract)*
+
+
+IInterfaceWithMethods (interface)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. py:class:: IInterfaceWithMethods
+
+ **Language-specific names:**
+
+ .. tabs::
+
+ .. code-tab:: c#
+
+ using Amazon.JSII.Tests.CalculatorNamespace;
+
+ .. code-tab:: java
+
+ import software.amazon.jsii.tests.calculator.IInterfaceWithMethods;
+
+ .. code-tab:: javascript
+
+ // IInterfaceWithMethods is an interface
+
+ .. code-tab:: typescript
+
+ import { IInterfaceWithMethods } from 'jsii-calc';
+
+
+
+
+
+ .. py:attribute:: value
+
+ :type: string *(readonly)* *(abstract)*
+
+
+ .. py:method:: doThings()
+
+ :abstract: Yes
+
+
IInterfaceWithProperties (interface)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts
index e12355e137..c289900ca9 100644
--- a/packages/jsii/lib/assembler.ts
+++ b/packages/jsii/lib/assembler.ts
@@ -11,6 +11,7 @@ import literate = require('./literate');
import { ProjectInfo } from './project-info';
import utils = require('./utils');
import { Validator } from './validator';
+import { NamedTypeReference, isInterfaceType } from 'jsii-spec';
// tslint:disable:no-var-requires Modules without TypeScript definitions
const sortJson = require('sort-json');
@@ -23,7 +24,7 @@ const LOG = log4js.getLogger('jsii/assembler');
*/
export class Assembler implements Emitter {
private _diagnostics = new Array();
- private _deferred = new Array<() => void>();
+ private _deferred = new Array();
private _types: { [fqn: string]: spec.Type };
/**
@@ -75,9 +76,8 @@ export class Assembler implements Emitter {
await this._visitNode(node.declarations[0]);
}
}
- for (const deferred of this._deferred) {
- deferred();
- }
+
+ this.callDeferredsInOrder();
// Skip emitting if any diagnostic message is an error
if (this._diagnostics.find(diag => diag.category === ts.DiagnosticCategory.Error) != null) {
@@ -87,9 +87,8 @@ export class Assembler implements Emitter {
try {
return { diagnostics: this._diagnostics, emitSkipped: true };
} finally {
- // Clearing ``this._diagnostics`` and ``this._deferred`` to allow contents to be garbage-collected.
+ // Clearing ``this._diagnostics`` to allow contents to be garbage-collected.
delete this._diagnostics;
- delete this._deferred;
}
}
@@ -128,9 +127,8 @@ export class Assembler implements Emitter {
// Clearing ``this._types`` to allow contents to be garbage-collected.
delete this._types;
- // Clearing ``this._diagnostics`` and ``this._deferred`` to allow contents to be garbage-collected.
+ // Clearing ``this._diagnostics`` to allow contents to be garbage-collected.
delete this._diagnostics;
- delete this._deferred;
}
async function _loadReadme(this: Assembler) {
@@ -144,15 +142,45 @@ export class Assembler implements Emitter {
}
}
+ /**
+ * Defer a callback until a (set of) types are available
+ *
+ * This is a helper function around _defer() which encapsulates the _dereference
+ * action (which is basically the majority use case for _defer anyway).
+ *
+ * Will not invoke the function with any 'undefined's; an error will already have been emitted in
+ * that case anyway.
+ */
+ // tslint:disable-next-line:max-line-length
+ private _deferUntilTypesAvailable(fqn: string, baseTypes: NamedTypeReference[], referencingNode: ts.Node, cb: (...xs: spec.Type[]) => void) {
+ // We can do this one eagerly
+ if (baseTypes.length === 0) {
+ cb();
+ return;
+ }
+
+ this._defer(fqn, baseTypes.map(x => x.fqn), () => {
+ const resolved = baseTypes.map(x => this._dereference(x, referencingNode)).filter(x => x !== undefined);
+ if (resolved.length > 0) {
+ return cb(...resolved as spec.Type[]);
+ }
+ });
+ }
+
/**
* Defer checks for after the program has been entirely processed; useful for verifying type references that may not
* have been discovered yet, and verifying properties about them.
*
+ * The callback is guaranteed to be executed only after all deferreds for all types in 'dependedFqns' have
+ * been executed.
+ *
+ * @param fqn FQN of the current type.
+ * @param deps List of FQNs of types this callback depends on. All deferreds for all
* @param cb the function to be called in a deferred way. It will be bound with ``this``, so it can depend on using
* ``this``.
*/
- private _defer(cb: () => void) {
- this._deferred.push(cb.bind(this));
+ private _defer(fqn: string, dependedFqns: string[], cb: () => void) {
+ this._deferred.push({ fqn, dependedFqns, cb: cb.bind(this) });
}
/**
@@ -299,9 +327,11 @@ export class Assembler implements Emitter {
LOG.trace(`Processing class: ${colors.gray(namespace.join('.'))}.${colors.cyan(type.symbol.name)}`);
}
+ const fqn = `${[this.projectInfo.name, ...namespace].join('.')}.${type.symbol.name}`;
+
const jsiiType: spec.ClassType = {
assembly: this.projectInfo.name,
- fqn: `${[this.projectInfo.name, ...namespace].join('.')}.${type.symbol.name}`,
+ fqn,
kind: spec.TypeKind.Class,
name: type.symbol.name,
namespace: namespace.join('.')
@@ -322,9 +352,8 @@ export class Assembler implements Emitter {
`Base type of ${jsiiType.fqn} is not a named type (${spec.describeTypeReference(ref)})`);
continue;
}
- this._defer(() => {
- const deref = this._dereference(ref, base.symbol.valueDeclaration);
- if (deref && !spec.isClassType(deref)) {
+ this._deferUntilTypesAvailable(fqn, [ref], base.symbol.valueDeclaration, (deref) => {
+ if (!spec.isClassType(deref)) {
this._diagnostic(base.symbol.valueDeclaration,
ts.DiagnosticCategory.Error,
`Base type of ${jsiiType.fqn} is not a class (${spec.describeTypeReference(ref)})`);
@@ -349,9 +378,8 @@ export class Assembler implements Emitter {
`Interface of ${jsiiType.fqn} is not a named type (${spec.describeTypeReference(typeRef)})`);
continue;
}
- this._defer(() => {
- const deref = this._dereference(typeRef, expression);
- if (deref && !spec.isInterfaceType(deref)) {
+ this._deferUntilTypesAvailable(fqn, [typeRef], expression, (deref) => {
+ if (!spec.isInterfaceType(deref)) {
this._diagnostic(expression,
ts.DiagnosticCategory.Error,
`Implements clause of ${jsiiType.fqn} uses ${spec.describeTypeReference(typeRef)} as an interface`);
@@ -410,16 +438,13 @@ export class Assembler implements Emitter {
}
}
} else if (jsiiType.base) {
- this._defer(() => {
- const baseType = this._dereference(jsiiType.base!, type.symbol.valueDeclaration);
- if (baseType) {
- if (spec.isClassType(baseType)) {
- jsiiType.initializer = baseType.initializer;
- } else {
- this._diagnostic(type.symbol.valueDeclaration,
- ts.DiagnosticCategory.Error,
- `Base type of ${jsiiType.fqn} (${jsiiType.base!.fqn}) is not a class`);
- }
+ this._deferUntilTypesAvailable(fqn, [jsiiType.base!], type.symbol.valueDeclaration, (baseType) => {
+ if (spec.isClassType(baseType)) {
+ jsiiType.initializer = baseType.initializer;
+ } else {
+ this._diagnostic(type.symbol.valueDeclaration,
+ ts.DiagnosticCategory.Error,
+ `Base type of ${jsiiType.fqn} (${jsiiType.base!.fqn}) is not a class`);
}
});
} else {
@@ -499,13 +524,16 @@ export class Assembler implements Emitter {
LOG.trace(`Processing interface: ${colors.gray(namespace.join('.'))}.${colors.cyan(type.symbol.name)}`);
}
+ const fqn = `${[this.projectInfo.name, ...namespace].join('.')}.${type.symbol.name}`;
+
const jsiiType: spec.InterfaceType = {
assembly: this.projectInfo.name,
- fqn: `${[this.projectInfo.name, ...namespace].join('.')}.${type.symbol.name}`,
+ fqn,
kind: spec.TypeKind.Interface,
name: type.symbol.name,
namespace: namespace.join('.')
};
+
for (const base of (type.getBaseTypes() || [])) {
const ref = await this._typeReference(base, type.symbol.valueDeclaration);
if (!spec.isNamedTypeReference(ref)) {
@@ -514,9 +542,8 @@ export class Assembler implements Emitter {
`Base type of ${jsiiType.fqn} is not a named type (${spec.describeTypeReference(ref)})`);
continue;
}
- this._defer(() => {
- const baseType = this._dereference(ref, base.symbol.valueDeclaration);
- if (baseType && !spec.isInterfaceType(baseType)) {
+ this._deferUntilTypesAvailable(fqn, [ref], base.symbol.valueDeclaration, (baseType) => {
+ if (!spec.isInterfaceType(baseType)) {
// tslint:disable:max-line-length
this._diagnostic(base.symbol.valueDeclaration,
ts.DiagnosticCategory.Error,
@@ -545,9 +572,20 @@ export class Assembler implements Emitter {
`Ignoring un-handled ${ts.SyntaxKind[member.valueDeclaration.kind]} member`);
}
}
- if ((jsiiType.methods || []).length === 0 && (jsiiType.properties || []).length > 0) {
- jsiiType.datatype = true;
- }
+
+ // Calculate datatype based on the datatypeness of this interface and all of its parents
+ // To keep the spec minimal the actual values of the attribute are "true" or "undefined" (to represent "false").
+ // tslint:disable-next-line:no-console
+ this._deferUntilTypesAvailable(fqn, jsiiType.interfaces || [], type.symbol.valueDeclaration, (...bases: spec.Type[]) => {
+ if ((jsiiType.methods || []).length === 0 && (jsiiType.properties || []).length > 0) {
+ jsiiType.datatype = true;
+ }
+ for (const base of bases) {
+ if (isInterfaceType(base) && !base.datatype) {
+ jsiiType.datatype = undefined;
+ }
+ }
+ });
return _sortMembers(this._visitDocumentation(type.symbol, jsiiType));
}
@@ -780,6 +818,41 @@ export class Assembler implements Emitter {
: { union: { types }, optional };
}
}
+
+ private callDeferredsInOrder() {
+ // Do a topological call order of all deferreds.
+ while (this._deferred.length > 0) {
+ // All fqns in dependency lists that don't have any pending
+ // deferreds themselves can be executed now, so are removed from
+ // dependency lists.
+ const pendingFqns = new Set(this._deferred.map(x => x.fqn));
+ for (const deferred of this._deferred) {
+ restrictDependenciesTo(deferred, pendingFqns);
+ }
+
+ // Invoke all deferreds with no more dependencies and remove them from the list.
+ let invoked = false;
+ for (let i = 0; i < this._deferred.length; i++) {
+ if (this._deferred[i].dependedFqns.length === 0) {
+ const deferred = this._deferred.splice(i, 1)[0];
+ deferred.cb();
+ invoked = true;
+ }
+ }
+
+ if (!invoked) {
+ // Apparently we're stuck. Complain loudly.
+ throw new Error(`Could not invoke any more deferreds, cyclic dependency? Remaining: ${JSON.stringify(this._deferred, undefined, 2)}`);
+ }
+ }
+
+ /**
+ * Retain only elements in the dependencyfqn that are also in the set
+ */
+ function restrictDependenciesTo(def: DeferredRecord, fqns: Set) {
+ def.dependedFqns = def.dependedFqns.filter(fqns.has.bind(fqns));
+ }
+ }
}
/**
@@ -913,3 +986,26 @@ function _toDependencies(assemblies: ReadonlyArray): { [name: str
}
return result;
}
+
+/**
+ * Deferred processing that needs to happen in a second, ordered pass
+ */
+interface DeferredRecord {
+ /**
+ * The FQN of the type the action will be executed on
+ */
+ fqn: string;
+
+ /**
+ * Dependency FQNs of the types that need to be processed before analysis.
+ *
+ * All deferred analysis actions for the types listed here must be complete
+ * before this analysis action can run.
+ */
+ dependedFqns: string[];
+
+ /**
+ * Callback representing the action to run.
+ */
+ cb: () => void;
+}
\ No newline at end of file