Skip to content

Commit 39923a5

Browse files
authored
feat(pacmak): allow opt-out of runtime type checking generation (#3710)
Adds a new CLI flag `--no-runtime-type-checking` to allow library authors to opt out of generating runtime type checking code if they are not interested in those. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent e7fadc0 commit 39923a5

File tree

18 files changed

+4664
-54
lines changed

18 files changed

+4664
-54
lines changed

packages/jsii-pacmak/bin/jsii-pacmak.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ import { VERSION_DESC } from '../lib/version';
4444
desc: 'generate code only (instead of building and packaging)',
4545
default: false,
4646
})
47+
.option('runtime-type-checking', {
48+
type: 'boolean',
49+
desc: [
50+
'generate runtime type checking code where compile-time type checking is not possible.',
51+
'Disabling this will generate less code, but will produce less helpful error messages when',
52+
'developers pass invalid values to the generated bindings.',
53+
].join(' '),
54+
default: true,
55+
})
4756
.option('fingerprint', {
4857
type: 'boolean',
4958
desc: 'attach a fingerprint to the generated artifacts, and skip generation if outdir contains artifacts that have a matching fingerprint',
@@ -171,6 +180,7 @@ import { VERSION_DESC } from '../lib/version';
171180
recurse: argv.recurse,
172181
rosettaUnknownSnippets,
173182
rosettaTablet: argv['rosetta-tablet'],
183+
runtimeTypeChecking: argv['runtime-type-checking'],
174184
targets: argv.targets?.map((target) => target as TargetName),
175185
updateNpmIgnoreFiles: argv.npmignore,
176186
validateAssemblies: argv['validate-assemblies'],

packages/jsii-pacmak/lib/builder.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,44 @@ export interface BuildOptions {
1313
* Whether to fingerprint the produced artifacts.
1414
* @default true
1515
*/
16-
fingerprint?: boolean;
16+
readonly fingerprint?: boolean;
1717

1818
/**
1919
* Whether artifacts should be re-build even if their fingerprints look up-to-date.
2020
* @default false
2121
*/
22-
force?: boolean;
22+
readonly force?: boolean;
2323

2424
/**
2525
* Arguments provided by the user (how they are used is target-dependent)
2626
*/
27-
arguments: { readonly [name: string]: any };
27+
readonly arguments: { readonly [name: string]: any };
2828

2929
/**
3030
* Only generate code, don't build
3131
*/
32-
codeOnly?: boolean;
32+
readonly codeOnly?: boolean;
3333

3434
/**
3535
* Whether or not to clean
3636
*/
37-
clean?: boolean;
37+
readonly clean?: boolean;
3838

3939
/**
4040
* Whether to add an additional subdirectory for the target language
4141
*/
42-
languageSubdirectory?: boolean;
42+
readonly languageSubdirectory?: boolean;
4343

4444
/**
4545
* The Rosetta instance to load examples from
4646
*/
47-
rosetta: Rosetta;
47+
readonly rosetta: Rosetta;
48+
49+
/**
50+
* Whether to generate runtime type checking code in places where compile-time
51+
* type checking is not possible.
52+
*/
53+
readonly runtimeTypeChecking: boolean;
4854
}
4955

5056
/**
@@ -130,13 +136,14 @@ export class IndependentPackageBuilder implements TargetBuilder {
130136

131137
private makeTarget(module: JsiiModule, options: BuildOptions): Target {
132138
return new this.targetConstructor({
133-
targetName: this.targetName,
134-
packageDir: module.moduleDirectory,
139+
arguments: options.arguments,
135140
assembly: module.assembly,
136141
fingerprint: options.fingerprint,
137142
force: options.force,
138-
arguments: options.arguments,
143+
packageDir: module.moduleDirectory,
139144
rosetta: options.rosetta,
145+
runtimeTypeChecking: options.runtimeTypeChecking,
146+
targetName: this.targetName,
140147
});
141148
}
142149

packages/jsii-pacmak/lib/generator.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export interface GeneratorOptions {
2929
* If this property is set, the generator will add "Base" to abstract class names
3030
*/
3131
addBasePostfixToAbstractClassNames?: boolean;
32+
33+
/**
34+
* If this property is set, the generator will add runtime type checking code in places
35+
* where compile-time type checking is not possible.
36+
*/
37+
runtimeTypeChecking: boolean;
3238
}
3339

3440
export interface IGenerator {
@@ -88,7 +94,11 @@ export abstract class Generator implements IGenerator {
8894
protected _reflectAssembly?: reflect.Assembly;
8995
private fingerprint?: string;
9096

91-
public constructor(private readonly options: GeneratorOptions = {}) {}
97+
public constructor(private readonly options: GeneratorOptions) {}
98+
99+
protected get runtimeTypeChecking() {
100+
return this.options.runtimeTypeChecking;
101+
}
92102

93103
protected get assembly(): spec.Assembly {
94104
if (!this._assembly) {

packages/jsii-pacmak/lib/index.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ export async function pacmak({
3232
parallel = true,
3333
recurse = false,
3434
rosettaTablet,
35+
rosettaUnknownSnippets = undefined,
36+
runtimeTypeChecking = true,
3537
targets = Object.values(TargetName),
3638
timers = new Timers(),
37-
rosettaUnknownSnippets = undefined,
3839
updateNpmIgnoreFiles = false,
3940
validateAssemblies = false,
4041
}: PacmakOptions): Promise<void> {
@@ -126,6 +127,7 @@ export async function pacmak({
126127
force,
127128
perLanguageDirectory,
128129
rosetta,
130+
runtimeTypeChecking,
129131
},
130132
),
131133
)
@@ -251,6 +253,13 @@ export interface PacmakOptions {
251253
*/
252254
readonly rosettaTablet?: string;
253255

256+
/**
257+
* Whether to inject runtime type checks in places where compile-time type checking is not performed.
258+
*
259+
* @default true
260+
*/
261+
readonly runtimeTypeChecking?: boolean;
262+
254263
/**
255264
* The list of targets for which code should be generated. Unless `forceTarget` is `true`, a given target will only
256265
* be generated for assemblies that have configured it.
@@ -295,6 +304,7 @@ async function buildTargetsForLanguage(
295304
force,
296305
perLanguageDirectory,
297306
rosetta,
307+
runtimeTypeChecking,
298308
}: {
299309
argv: { readonly [name: string]: any };
300310
clean: boolean;
@@ -303,6 +313,7 @@ async function buildTargetsForLanguage(
303313
force: boolean;
304314
perLanguageDirectory: boolean;
305315
rosetta: Rosetta;
316+
runtimeTypeChecking: boolean;
306317
},
307318
): Promise<void> {
308319
// ``argv.target`` is guaranteed valid by ``yargs`` through the ``choices`` directive.
@@ -312,13 +323,14 @@ async function buildTargetsForLanguage(
312323
}
313324

314325
return factory(modules, {
326+
arguments: argv,
315327
clean: clean,
316328
codeOnly: codeOnly,
317-
rosetta,
318-
force: force,
319329
fingerprint: fingerprint,
320-
arguments: argv,
330+
force: force,
321331
languageSubdirectory: perLanguageDirectory,
332+
rosetta,
333+
runtimeTypeChecking,
322334
}).buildModules();
323335
}
324336

packages/jsii-pacmak/lib/target.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ export abstract class Target {
1717
protected readonly targetName: string;
1818
protected readonly assembly: reflect.Assembly;
1919
protected readonly rosetta: Rosetta;
20+
protected readonly runtimeTypeChecking: boolean;
2021

2122
protected abstract readonly generator: IGenerator;
2223

2324
public constructor(options: TargetOptions) {
24-
this.packageDir = options.packageDir;
25+
this.arguments = options.arguments;
2526
this.assembly = options.assembly;
26-
this.rosetta = options.rosetta;
2727
this.fingerprint = options.fingerprint ?? true;
2828
this.force = options.force ?? false;
29-
this.arguments = options.arguments;
29+
this.packageDir = options.packageDir;
30+
this.rosetta = options.rosetta;
31+
this.runtimeTypeChecking = options.runtimeTypeChecking;
3032
this.targetName = options.targetName;
3133
}
3234

@@ -211,6 +213,9 @@ export interface TargetOptions {
211213
/** The Rosetta instance */
212214
rosetta: Rosetta;
213215

216+
/** Whether to generate runtime type-checking code */
217+
runtimeTypeChecking: boolean;
218+
214219
/**
215220
* Whether to fingerprint the produced artifacts.
216221
* @default true

packages/jsii-pacmak/lib/targets/dotnet.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,14 @@ export class DotnetBuilder implements TargetBuilder {
240240
private makeTarget(module: JsiiModule): Dotnet {
241241
return new Dotnet(
242242
{
243-
targetName: this.targetName,
244-
packageDir: module.moduleDirectory,
243+
arguments: this.options.arguments,
245244
assembly: module.assembly,
246245
fingerprint: this.options.fingerprint,
247246
force: this.options.force,
248-
arguments: this.options.arguments,
247+
packageDir: module.moduleDirectory,
249248
rosetta: this.options.rosetta,
249+
runtimeTypeChecking: this.options.runtimeTypeChecking,
250+
targetName: this.targetName,
250251
},
251252
this.modules.map((m) => m.name),
252253
);
@@ -316,7 +317,7 @@ export default class Dotnet extends Target {
316317

317318
this.generator = new DotNetGenerator(
318319
assembliesCurrentlyBeingCompiled,
319-
options.rosetta,
320+
options,
320321
);
321322
}
322323

packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,35 @@ import { DotNetNameUtils } from './nameutils';
2121
* CODE GENERATOR V2
2222
*/
2323
export class DotNetGenerator extends Generator {
24+
private readonly nameutils: DotNetNameUtils = new DotNetNameUtils();
25+
26+
private readonly rosetta: Rosetta;
27+
2428
// Flags that tracks if we have already wrote the first member of the class
2529
private firstMemberWritten = false;
2630

2731
private typeresolver!: DotNetTypeResolver;
2832

29-
private readonly nameutils: DotNetNameUtils = new DotNetNameUtils();
30-
3133
private dotnetRuntimeGenerator!: DotNetRuntimeGenerator;
3234

3335
private dotnetDocGenerator!: DotNetDocGenerator;
3436

3537
public constructor(
3638
private readonly assembliesCurrentlyBeingCompiled: string[],
37-
private readonly rosetta: Rosetta,
39+
options: {
40+
readonly rosetta: Rosetta;
41+
readonly runtimeTypeChecking: boolean;
42+
},
3843
) {
39-
super();
44+
super(options);
4045

4146
// Override the openBlock to get a correct C# looking code block with the curly brace after the line
4247
this.code.openBlock = function (text) {
4348
this.line(text);
4449
this.open('{');
4550
};
51+
52+
this.rosetta = options.rosetta;
4653
}
4754

4855
public async load(
@@ -646,6 +653,11 @@ export class DotNetGenerator extends Generator {
646653
parameters?: readonly spec.Parameter[],
647654
{ noMangle = false }: { noMangle?: boolean } = {},
648655
): void {
656+
if (!this.runtimeTypeChecking) {
657+
// We were configured not to emit those, so bail out now.
658+
return;
659+
}
660+
649661
const unionParameters = parameters?.filter(({ type }) =>
650662
containsUnionType(type),
651663
);

packages/jsii-pacmak/lib/targets/java.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,14 @@ export class JavaBuilder implements TargetBuilder {
328328

329329
private makeTarget(module: JsiiModule, options: BuildOptions): Target {
330330
return new Java({
331-
targetName: this.targetName,
332-
packageDir: module.moduleDirectory,
331+
arguments: options.arguments,
333332
assembly: module.assembly,
334333
fingerprint: options.fingerprint,
335334
force: options.force,
336-
arguments: options.arguments,
335+
packageDir: module.moduleDirectory,
337336
rosetta: options.rosetta,
337+
runtimeTypeChecking: options.runtimeTypeChecking,
338+
targetName: this.targetName,
338339
});
339340
}
340341
}
@@ -421,7 +422,7 @@ export default class Java extends Target {
421422
public constructor(options: TargetOptions) {
422423
super(options);
423424

424-
this.generator = new JavaGenerator(options.rosetta);
425+
this.generator = new JavaGenerator(options);
425426
}
426427

427428
public async build(sourceDir: string, outDir: string): Promise<void> {
@@ -619,8 +620,14 @@ class JavaGenerator extends Generator {
619620
[name: string]: spec.AssemblyConfiguration;
620621
} = {};
621622

622-
public constructor(private readonly rosetta: Rosetta) {
623-
super({ generateOverloadsForMethodWithOptionals: true });
623+
private readonly rosetta: Rosetta;
624+
625+
public constructor(options: {
626+
readonly rosetta: Rosetta;
627+
readonly runtimeTypeChecking: boolean;
628+
}) {
629+
super({ ...options, generateOverloadsForMethodWithOptionals: true });
630+
this.rosetta = options.rosetta;
624631
}
625632

626633
protected onBeginAssembly(assm: spec.Assembly, fingerprint: boolean) {

packages/jsii-pacmak/lib/targets/js.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export default class JavaScript extends Target {
6262
// ##################
6363

6464
class PackOnly extends Generator {
65+
public constructor() {
66+
// NB: This does not generate code, so runtime type checking is irrelevant
67+
super({ runtimeTypeChecking: false });
68+
}
69+
6570
public async save(outdir: string, tarball: string, _: Legalese) {
6671
// Intentionally ignore the Legalese field here... it's not useful here.
6772
return super.save(outdir, tarball, {});

0 commit comments

Comments
 (0)