@noir-lang/noir_codegen 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { CompiledCircuit } from '@noir-lang/types';
2
- export declare const codegen: (programs: [string, CompiledCircuit][]) => string;
2
+ export declare const codegen: (programs: [string, CompiledCircuit][], embedArtifact: boolean, useFixedLengthArrays: boolean) => string;
package/lib/index.js CHANGED
@@ -1,47 +1,8 @@
1
- import { generateTsInterface, codegenStructDefinitions } from './noir_types.js';
2
- // TODO: reenable this. See `abiTypeToTs` for reasoning.
3
- // export type FixedLengthArray<T, L extends number> = L extends 0 ? never[]: T[] & { length: L };
4
- const codegenPrelude = `/* Autogenerated file, do not edit! */
5
-
6
- /* eslint-disable */
7
-
8
- import { Noir, InputMap, CompiledCircuit, ForeignCallHandler } from "@noir-lang/noir_js"
9
-
10
- export { ForeignCallHandler } from "@noir-lang/noir_js"
11
- `;
12
- const codegenFunction = (name, compiled_program, function_signature) => {
13
- const args = function_signature.inputs.map(([name]) => `${name}`).join(', ');
14
- const args_with_types = function_signature.inputs.map(([name, type]) => `${name}: ${type}`).join(', ');
15
- return `export const ${name}_circuit: CompiledCircuit = ${JSON.stringify(compiled_program)};
16
-
17
- export async function ${name}(${args_with_types}, foreignCallHandler?: ForeignCallHandler): Promise<${function_signature.returnValue}> {
18
- const program = new Noir(${name}_circuit);
19
- const args: InputMap = { ${args} };
20
- const { returnValue } = await program.execute(args, foreignCallHandler);
21
- return returnValue as ${function_signature.returnValue};
22
- }
23
- `;
1
+ import { TypingsGenerator } from './utils/typings_generator.js';
2
+ export const codegen = (programs, embedArtifact, useFixedLengthArrays) => {
3
+ return new TypingsGenerator(programs.map((program) => ({
4
+ circuitName: program[0],
5
+ artifact: embedArtifact ? program[1] : undefined,
6
+ abi: structuredClone(program[1].abi), // We'll mutate the ABI types when doing typescript codegen, so we clone it to avoid mutating the artifact.
7
+ })), useFixedLengthArrays).codegen();
24
8
  };
25
- export const codegen = (programs) => {
26
- let results = [codegenPrelude];
27
- const primitiveTypeMap = new Map();
28
- const structTypeMap = new Map();
29
- const functions = [];
30
- for (const [name, program] of programs) {
31
- const function_sig = generateTsInterface(program.abi, structTypeMap, primitiveTypeMap);
32
- functions.push(codegenFunction(name, stripUnwantedFields(program), function_sig));
33
- }
34
- const structTypeDefinitions = codegenStructDefinitions(structTypeMap, primitiveTypeMap);
35
- // Add the primitive Noir types that do not have a 1-1 mapping to TypeScript.
36
- const primitiveTypeAliases = [];
37
- for (const value of primitiveTypeMap.values()) {
38
- primitiveTypeAliases.push(`export type ${value.aliasName} = ${value.tsType};`);
39
- }
40
- results = results.concat(...primitiveTypeAliases, '', structTypeDefinitions, ...functions);
41
- return results.join('\n');
42
- };
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- function stripUnwantedFields(value) {
45
- const { abi, bytecode } = value;
46
- return { abi, bytecode };
47
- }
package/lib/main.js CHANGED
@@ -17,7 +17,7 @@ function main() {
17
17
  const { abi, bytecode } = JSON.parse(file_contents);
18
18
  return [program_name, { abi, bytecode }];
19
19
  });
20
- const result = codegen(programs);
20
+ const result = codegen(programs, !cliConfig.externalArtifact, cliConfig.useFixedLengthArrays);
21
21
  const outputDir = path.resolve(cliConfig.outDir ?? './codegen');
22
22
  const outputFile = path.join(outputDir, 'index.ts');
23
23
  if (!fs.existsSync(outputDir))
@@ -2,5 +2,7 @@ export interface ParsedArgs {
2
2
  files: string[];
3
3
  outDir?: string | undefined;
4
4
  inputDir?: string | undefined;
5
+ externalArtifact: boolean;
6
+ useFixedLengthArrays: boolean;
5
7
  }
6
8
  export declare function parseArgs(): ParsedArgs;
package/lib/parseArgs.js CHANGED
@@ -16,6 +16,16 @@ export function parseArgs() {
16
16
  description: 'Directory containing program artifact files. Inferred as lowest common path of all files if not specified.',
17
17
  },
18
18
  help: { type: Boolean, defaultValue: false, alias: 'h', description: 'Prints this message.' },
19
+ 'external-artifact': {
20
+ type: Boolean,
21
+ defaultValue: false,
22
+ description: 'Does not embed the circuit artifact in the code, instead requiring passing the circuit artifact as an argument to the generated functions.',
23
+ },
24
+ 'fixed-length-arrays': {
25
+ type: Boolean,
26
+ defaultValue: false,
27
+ description: 'Use fixed-length arrays for inputs and outputs.',
28
+ },
19
29
  }, {
20
30
  helpArg: 'help',
21
31
  headerContentSections: [
@@ -39,5 +49,7 @@ export function parseArgs() {
39
49
  files: rawOptions.glob,
40
50
  outDir: rawOptions['out-dir'],
41
51
  inputDir: rawOptions['input-dir'],
52
+ externalArtifact: rawOptions['external-artifact'],
53
+ useFixedLengthArrays: rawOptions['fixed-length-arrays'],
42
54
  };
43
55
  }
@@ -0,0 +1,70 @@
1
+ import { AbiType } from '@noir-lang/noirc_abi';
2
+ /**
3
+ * Represents a binding to a generic.
4
+ */
5
+ export declare class BindingId {
6
+ id: number;
7
+ isNumeric: boolean;
8
+ constructor(id: number, isNumeric: boolean);
9
+ }
10
+ export type StructType = {
11
+ path: string;
12
+ fields: {
13
+ name: string;
14
+ type: AbiTypeWithGenerics;
15
+ }[];
16
+ /** The generics of the struct, bound to the fields */
17
+ generics: BindingId[];
18
+ };
19
+ export type StringType = {
20
+ kind: 'string';
21
+ length: number | BindingId | null;
22
+ };
23
+ export type Constant = {
24
+ kind: 'constant';
25
+ value: number;
26
+ };
27
+ export type ArrayType = {
28
+ kind: 'array';
29
+ length: number | BindingId | null;
30
+ type: AbiTypeWithGenerics;
31
+ };
32
+ export type Tuple = {
33
+ kind: 'tuple';
34
+ fields: AbiTypeWithGenerics[];
35
+ };
36
+ export type Struct = {
37
+ kind: 'struct';
38
+ structType: StructType;
39
+ /** The arguments are the concrete instantiation of the generics in the struct type. */
40
+ args: AbiTypeWithGenerics[];
41
+ };
42
+ export type AbiTypeWithGenerics = {
43
+ kind: 'field';
44
+ } | {
45
+ kind: 'boolean';
46
+ } | {
47
+ kind: 'integer';
48
+ sign: string;
49
+ width: number;
50
+ } | {
51
+ kind: 'binding';
52
+ id: BindingId;
53
+ } | {
54
+ kind: 'constant';
55
+ value: number;
56
+ } | StringType | ArrayType | Tuple | Struct;
57
+ /**
58
+ * Maps an ABI type to an ABI type with generics.
59
+ * This performs pure type conversion, and does not generate any bindings.
60
+ */
61
+ export declare function mapAbiTypeToAbiTypeWithGenerics(abiType: AbiType): AbiTypeWithGenerics;
62
+ /**
63
+ * Finds the structs in an ABI type.
64
+ * This won't explore nested structs.
65
+ */
66
+ export declare function findStructsInType(abiType: AbiTypeWithGenerics): Struct[];
67
+ /**
68
+ * Finds all the structs in an ABI type, including nested structs.
69
+ */
70
+ export declare function findAllStructsInType(abiType: AbiTypeWithGenerics): Struct[];
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Represents a binding to a generic.
3
+ */
4
+ export class BindingId {
5
+ id;
6
+ isNumeric;
7
+ constructor(id, isNumeric) {
8
+ this.id = id;
9
+ this.isNumeric = isNumeric;
10
+ }
11
+ }
12
+ /**
13
+ * Maps an ABI type to an ABI type with generics.
14
+ * This performs pure type conversion, and does not generate any bindings.
15
+ */
16
+ export function mapAbiTypeToAbiTypeWithGenerics(abiType) {
17
+ switch (abiType.kind) {
18
+ case 'field':
19
+ case 'boolean':
20
+ case 'string':
21
+ case 'integer':
22
+ return abiType;
23
+ case 'array':
24
+ return {
25
+ kind: 'array',
26
+ length: abiType.length,
27
+ type: mapAbiTypeToAbiTypeWithGenerics(abiType.type),
28
+ };
29
+ case 'struct': {
30
+ const structType = {
31
+ path: abiType.path,
32
+ fields: abiType.fields.map((field) => ({
33
+ name: field.name,
34
+ type: mapAbiTypeToAbiTypeWithGenerics(field.type),
35
+ })),
36
+ generics: [],
37
+ };
38
+ return {
39
+ kind: 'struct',
40
+ structType,
41
+ args: [],
42
+ };
43
+ }
44
+ case 'tuple':
45
+ return {
46
+ kind: 'tuple',
47
+ fields: abiType.fields.map(mapAbiTypeToAbiTypeWithGenerics),
48
+ };
49
+ default: {
50
+ const exhaustiveCheck = abiType;
51
+ throw new Error(`Unhandled abi type: ${exhaustiveCheck}`);
52
+ }
53
+ }
54
+ }
55
+ /**
56
+ * Finds the structs in an ABI type.
57
+ * This won't explore nested structs.
58
+ */
59
+ export function findStructsInType(abiType) {
60
+ switch (abiType.kind) {
61
+ case 'field':
62
+ case 'boolean':
63
+ case 'string':
64
+ case 'integer':
65
+ return [];
66
+ case 'array':
67
+ return findStructsInType(abiType.type);
68
+ case 'tuple':
69
+ return abiType.fields.flatMap(findStructsInType);
70
+ case 'struct':
71
+ return [abiType];
72
+ default: {
73
+ return [];
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Finds all the structs in an ABI type, including nested structs.
79
+ */
80
+ export function findAllStructsInType(abiType) {
81
+ let allStructs = [];
82
+ let lastStructs = findStructsInType(abiType);
83
+ while (lastStructs.length > 0) {
84
+ allStructs = allStructs.concat(lastStructs);
85
+ lastStructs = lastStructs.flatMap((struct) => struct.structType.fields.flatMap((field) => findStructsInType(field.type)));
86
+ }
87
+ return allStructs;
88
+ }
@@ -0,0 +1,46 @@
1
+ import { type AbiTypeWithGenerics, type StructType } from './abi_type_with_generics.js';
2
+ export interface DemonomorphizerConfig {
3
+ leaveArrayLengthsUnbounded: boolean;
4
+ leaveStringLengthsUnbounded: boolean;
5
+ }
6
+ /**
7
+ * Demonomorphizes a list of ABI types adding generics to structs.
8
+ * Since monomorphization of the generics destroys information, this process is not guaranteed to return the original structure.
9
+ * However, it should successfully unify all struct types that share the same name and field names.
10
+ */
11
+ export declare class Demonomorphizer {
12
+ private types;
13
+ private config;
14
+ private variantsMap;
15
+ private visitedStructs;
16
+ private lastBindingId;
17
+ /**
18
+ * Demonomorphizes the passed in ABI types, mutating them.
19
+ */
20
+ static demonomorphize(abiTypes: AbiTypeWithGenerics[], config: DemonomorphizerConfig): void;
21
+ private constructor();
22
+ /**
23
+ * Finds all the variants of the structs in the types.
24
+ * A variant is every use of a struct with the same name and fields.
25
+ */
26
+ private fillVariantsMap;
27
+ private demonomorphizeStructs;
28
+ /**
29
+ * Demonomorphizes a struct, by demonomorphizing its dependencies first.
30
+ * Then it'll unify the types of the variants generating a unique generic type.
31
+ * It'll also generate args that instantiate the generic type with the concrete arguments for each variant.
32
+ */
33
+ private demonomorphizeStruct;
34
+ /**
35
+ * Tries to unify the types of a set of variants recursively.
36
+ * Unification will imply replacing some properties with bindings and pushing bindings to the generics of the struct.
37
+ */
38
+ private unifyTypes;
39
+ /**
40
+ * We consider a struct to be the same if it has the same name and field names.
41
+ * Structs with the same id will be unified into a single type by the demonomorphizer.
42
+ */
43
+ static buildIdForStruct(struct: StructType): string;
44
+ private buildBindingAndPushToVariants;
45
+ private buildNumericBindingAndPushToVariants;
46
+ }
@@ -0,0 +1,223 @@
1
+ import { BindingId, findAllStructsInType, findStructsInType, } from './abi_type_with_generics.js';
2
+ /**
3
+ * Demonomorphizes a list of ABI types adding generics to structs.
4
+ * Since monomorphization of the generics destroys information, this process is not guaranteed to return the original structure.
5
+ * However, it should successfully unify all struct types that share the same name and field names.
6
+ */
7
+ export class Demonomorphizer {
8
+ types;
9
+ config;
10
+ variantsMap;
11
+ visitedStructs;
12
+ lastBindingId = 0;
13
+ /**
14
+ * Demonomorphizes the passed in ABI types, mutating them.
15
+ */
16
+ static demonomorphize(abiTypes, config) {
17
+ new Demonomorphizer(abiTypes, config);
18
+ }
19
+ constructor(types, config) {
20
+ this.types = types;
21
+ this.config = config;
22
+ this.variantsMap = new Map();
23
+ this.fillVariantsMap();
24
+ this.visitedStructs = new Map();
25
+ this.demonomorphizeStructs();
26
+ }
27
+ /**
28
+ * Finds all the variants of the structs in the types.
29
+ * A variant is every use of a struct with the same name and fields.
30
+ */
31
+ fillVariantsMap() {
32
+ const allStructs = this.types.flatMap(findAllStructsInType);
33
+ for (const struct of allStructs) {
34
+ const id = Demonomorphizer.buildIdForStruct(struct.structType);
35
+ const variants = this.variantsMap.get(id) ?? [];
36
+ variants.push(struct);
37
+ this.variantsMap.set(id, variants);
38
+ }
39
+ }
40
+ demonomorphizeStructs() {
41
+ for (const type of this.types) {
42
+ const topLevelStructs = findStructsInType(type);
43
+ for (const struct of topLevelStructs) {
44
+ this.demonomorphizeStruct(struct);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Demonomorphizes a struct, by demonomorphizing its dependencies first.
50
+ * Then it'll unify the types of the variants generating a unique generic type.
51
+ * It'll also generate args that instantiate the generic type with the concrete arguments for each variant.
52
+ */
53
+ demonomorphizeStruct(struct) {
54
+ const id = Demonomorphizer.buildIdForStruct(struct.structType);
55
+ if (this.visitedStructs.has(id)) {
56
+ return;
57
+ }
58
+ const dependencies = struct.structType.fields.flatMap((field) => findStructsInType(field.type));
59
+ for (const dependency of dependencies) {
60
+ this.demonomorphizeStruct(dependency);
61
+ }
62
+ if (this.visitedStructs.has(id)) {
63
+ throw new Error('Circular dependency detected');
64
+ }
65
+ const variants = this.variantsMap.get(id);
66
+ const mappedStructType = struct.structType;
67
+ for (let i = 0; i < struct.structType.fields.length; i++) {
68
+ const variantTypes = variants.map((variant) => variant.structType.fields[i].type);
69
+ const mappedType = this.unifyTypes(variantTypes, mappedStructType.generics, variants);
70
+ mappedStructType.fields[i].type = mappedType;
71
+ }
72
+ // Mutate variants setting the new struct type
73
+ variants.forEach((variant) => (variant.structType = mappedStructType));
74
+ this.visitedStructs.set(id, mappedStructType);
75
+ }
76
+ /**
77
+ * Tries to unify the types of a set of variants recursively.
78
+ * Unification will imply replacing some properties with bindings and pushing bindings to the generics of the struct.
79
+ */
80
+ unifyTypes(types, generics, // Mutates generics adding new bindings
81
+ variants) {
82
+ const kinds = new Set(types.map((type) => type.kind));
83
+ if (kinds.size > 1) {
84
+ return this.buildBindingAndPushToVariants(types, generics, variants);
85
+ }
86
+ switch (types[0].kind) {
87
+ case 'field':
88
+ case 'boolean':
89
+ case 'binding':
90
+ return types[0];
91
+ case 'integer': {
92
+ if (allDeepEqual(types)) {
93
+ return types[0];
94
+ }
95
+ else {
96
+ return this.buildBindingAndPushToVariants(types, generics, variants);
97
+ }
98
+ }
99
+ case 'string': {
100
+ const strings = types;
101
+ const unifiedStringType = strings[0];
102
+ if (strings.every((string) => string.length === unifiedStringType.length)) {
103
+ return unifiedStringType;
104
+ }
105
+ else if (!this.config.leaveStringLengthsUnbounded) {
106
+ unifiedStringType.length = this.buildNumericBindingAndPushToVariants(strings.map((string) => {
107
+ if (typeof string.length !== 'number') {
108
+ throw new Error('Trying to unify strings with bindings');
109
+ }
110
+ return string.length;
111
+ }), generics, variants);
112
+ return unifiedStringType;
113
+ }
114
+ else {
115
+ unifiedStringType.length = null;
116
+ return unifiedStringType;
117
+ }
118
+ }
119
+ case 'array': {
120
+ const arrays = types;
121
+ const unifiedArrayType = arrays[0];
122
+ if (!arrays.every((array) => array.length === unifiedArrayType.length)) {
123
+ if (!this.config.leaveArrayLengthsUnbounded) {
124
+ unifiedArrayType.length = this.buildNumericBindingAndPushToVariants(arrays.map((array) => {
125
+ if (typeof array.length !== 'number') {
126
+ throw new Error('Trying to unify arrays with bindings');
127
+ }
128
+ return array.length;
129
+ }), generics, variants);
130
+ }
131
+ else {
132
+ unifiedArrayType.length = null;
133
+ }
134
+ }
135
+ unifiedArrayType.type = this.unifyTypes(arrays.map((array) => array.type), generics, variants);
136
+ return unifiedArrayType;
137
+ }
138
+ case 'tuple': {
139
+ const tuples = types;
140
+ const unifiedTupleType = tuples[0];
141
+ for (let i = 0; i < unifiedTupleType.fields.length; i++) {
142
+ unifiedTupleType.fields[i] = this.unifyTypes(tuples.map((tuple) => tuple.fields[i]), generics, variants);
143
+ }
144
+ return unifiedTupleType;
145
+ }
146
+ case 'struct': {
147
+ const structs = types;
148
+ const ids = new Set(structs.map((struct) => Demonomorphizer.buildIdForStruct(struct.structType)));
149
+ if (ids.size > 1) {
150
+ // If the types are different structs, we can only unify them by creating a new binding.
151
+ // For example, if we have a struct A { x: u32 } and a struct A { x: Field }, the only possible unification is A<T> { x: T }
152
+ return this.buildBindingAndPushToVariants(types, generics, variants);
153
+ }
154
+ else {
155
+ // If the types are the same struct, we must unify the arguments to the struct.
156
+ // For example, if we have A<Field> and A<u32>, we need to unify to A<T> and push T to the generics of the struct type.
157
+ const unifiedStruct = structs[0];
158
+ if (!structs.every((struct) => struct.args.length === unifiedStruct.args.length)) {
159
+ throw new Error('Same struct with different number of args encountered');
160
+ }
161
+ for (let i = 0; i < unifiedStruct.args.length; i++) {
162
+ const argTypes = structs.map((struct) => struct.args[i]);
163
+ unifiedStruct.args[i] = this.unifyTypes(argTypes, generics, variants);
164
+ }
165
+ return unifiedStruct;
166
+ }
167
+ }
168
+ case 'constant': {
169
+ const constants = types;
170
+ if (constants.every((constant) => constant.value === constants[0].value)) {
171
+ return constants[0];
172
+ }
173
+ else {
174
+ return this.buildBindingAndPushToVariants(types, generics, variants, true);
175
+ }
176
+ }
177
+ default: {
178
+ const exhaustiveCheck = types[0];
179
+ throw new Error(`Unhandled abi type: ${exhaustiveCheck}`);
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * We consider a struct to be the same if it has the same name and field names.
185
+ * Structs with the same id will be unified into a single type by the demonomorphizer.
186
+ */
187
+ static buildIdForStruct(struct) {
188
+ const name = struct.path.split('::').pop();
189
+ const fields = struct.fields.map((field) => field.name).join(',');
190
+ return `${name}(${fields})`;
191
+ }
192
+ buildBindingAndPushToVariants(concreteTypes, generics, variants, isNumeric = false) {
193
+ const bindingId = new BindingId(this.lastBindingId++, isNumeric);
194
+ for (let i = 0; i < variants.length; i++) {
195
+ const variant = variants[i];
196
+ const concreteType = concreteTypes[i];
197
+ variant.args.push(concreteType);
198
+ }
199
+ generics.push(bindingId);
200
+ return { kind: 'binding', id: bindingId };
201
+ }
202
+ buildNumericBindingAndPushToVariants(concreteNumbers, generics, variants) {
203
+ const bindingId = new BindingId(this.lastBindingId++, true);
204
+ for (let i = 0; i < variants.length; i++) {
205
+ const variant = variants[i];
206
+ variant.args.push({ kind: 'constant', value: concreteNumbers[i] });
207
+ }
208
+ generics.push(bindingId);
209
+ return bindingId;
210
+ }
211
+ }
212
+ function allDeepEqual(arr) {
213
+ if (arr.length === 0) {
214
+ return true;
215
+ }
216
+ const first = JSON.stringify(arr[0]);
217
+ for (let i = 0; i < arr.length; i++) {
218
+ if (JSON.stringify(arr[i]) !== first) {
219
+ return false;
220
+ }
221
+ }
222
+ return true;
223
+ }
@@ -0,0 +1,36 @@
1
+ import { CompiledCircuit } from '@noir-lang/types';
2
+ import { Abi } from '@noir-lang/noirc_abi';
3
+ export declare class TypingsGenerator {
4
+ private useFixedLengthArrays;
5
+ /** All the types in the ABIs */
6
+ private allTypes;
7
+ /** The demonomorphized ABIs of the circuits */
8
+ private demonomorphizedAbis;
9
+ /** Maps struct id to name for structs with the same name and different field sets */
10
+ private structIdToTsName;
11
+ /** Collect all the primitives used in the types to add them to the codegen */
12
+ private primitiveTypesUsed;
13
+ constructor(circuits: {
14
+ abi: Abi;
15
+ circuitName: string;
16
+ artifact?: CompiledCircuit;
17
+ }[], useFixedLengthArrays: boolean);
18
+ codegen(): string;
19
+ private codegenAllStructs;
20
+ private getStructName;
21
+ private codegenStructType;
22
+ private codegenType;
23
+ /**
24
+ * Typescript does not allow us to check for equality of non-primitive types
25
+ * easily, so we create a addIfUnique function that will only add an item
26
+ * to the map if it is not already there by using JSON.stringify.
27
+ * @param item - The item to add to the map.
28
+ */
29
+ private addIfUnique;
30
+ /**
31
+ * Codegen all the interfaces for the circuits.
32
+ * For a circuit named Foo, we'll codegen FooInputType and FooReturnType.
33
+ */
34
+ private codegenAllInterfaces;
35
+ private codegenAllPrimitives;
36
+ }
@@ -0,0 +1,254 @@
1
+ import { findAllStructsInType, mapAbiTypeToAbiTypeWithGenerics, } from './abi_type_with_generics.js';
2
+ import { Demonomorphizer } from './demonomorphizer.js';
3
+ const codegenPrelude = `/* Autogenerated file, do not edit! */
4
+
5
+ /* eslint-disable */
6
+
7
+ import { Noir, InputMap, CompiledCircuit, ForeignCallHandler } from "@noir-lang/noir_js"
8
+
9
+ export { ForeignCallHandler } from "@noir-lang/noir_js"
10
+ `;
11
+ /**
12
+ * Returns the last component of a path, e.g. "foo::bar::baz" -\> "baz"
13
+ * Note: that if we have a path such as "Baz", we will return "Baz".
14
+ *
15
+ * Since these paths corresponds to structs, we can assume that we
16
+ * cannot have "foo::bar::".
17
+ *
18
+ * We also make the assumption that since these paths are coming from
19
+ * Noir, then we will not have two paths that look like this:
20
+ * - foo::bar::Baz
21
+ * - cat::dog::Baz
22
+ * ie the last component of the path (struct name) is enough to uniquely identify
23
+ * the whole path.
24
+ *
25
+ * TODO: We should double check this assumption when we use type aliases,
26
+ * I expect that `foo::bar::Baz as Dog` would effectively give `foo::bar::Dog`
27
+ * @param str - The path to get the last component of.
28
+ * @returns The last component of the path.
29
+ */
30
+ function getLastComponentOfPath(str) {
31
+ const parts = str.split('::');
32
+ const lastPart = parts[parts.length - 1];
33
+ return lastPart;
34
+ }
35
+ /**
36
+ * Replaces a numeric binding with the corresponding generics name or the actual value.
37
+ */
38
+ function replaceNumericBinding(id, genericsNameMap) {
39
+ if (typeof id === 'number') {
40
+ return id.toString();
41
+ }
42
+ else {
43
+ return genericsNameMap.get(id.id) ?? 'unknown';
44
+ }
45
+ }
46
+ export class TypingsGenerator {
47
+ useFixedLengthArrays;
48
+ /** All the types in the ABIs */
49
+ allTypes = [];
50
+ /** The demonomorphized ABIs of the circuits */
51
+ demonomorphizedAbis = [];
52
+ /** Maps struct id to name for structs with the same name and different field sets */
53
+ structIdToTsName = new Map();
54
+ /** Collect all the primitives used in the types to add them to the codegen */
55
+ primitiveTypesUsed = new Map();
56
+ constructor(circuits, useFixedLengthArrays) {
57
+ this.useFixedLengthArrays = useFixedLengthArrays;
58
+ // Map all the types used in the ABIs to the demonomorphized types
59
+ for (const { abi, circuitName, artifact } of circuits) {
60
+ const params = abi.parameters.map((param) => {
61
+ const type = mapAbiTypeToAbiTypeWithGenerics(param.type);
62
+ this.allTypes.push(type);
63
+ return { name: param.name, type };
64
+ });
65
+ if (abi.return_type) {
66
+ const returnType = mapAbiTypeToAbiTypeWithGenerics(abi.return_type.abi_type);
67
+ this.allTypes.push(returnType);
68
+ this.demonomorphizedAbis.push({ circuitName, params, returnType, artifact });
69
+ }
70
+ else {
71
+ this.demonomorphizedAbis.push({ circuitName, params, artifact });
72
+ }
73
+ }
74
+ // Demonomorphize the types
75
+ Demonomorphizer.demonomorphize(this.allTypes, {
76
+ leaveArrayLengthsUnbounded: !useFixedLengthArrays,
77
+ leaveStringLengthsUnbounded: true,
78
+ });
79
+ }
80
+ codegen() {
81
+ this.primitiveTypesUsed = new Map();
82
+ const structsCode = this.codegenAllStructs();
83
+ const interfacesCode = this.codegenAllInterfaces();
84
+ const primitivesCode = this.codegenAllPrimitives();
85
+ return `
86
+ ${codegenPrelude}
87
+ ${primitivesCode}
88
+ ${structsCode}
89
+ ${interfacesCode}`;
90
+ }
91
+ codegenAllStructs() {
92
+ const allStructs = this.allTypes.flatMap(findAllStructsInType);
93
+ // First, deduplicate the structs used
94
+ const structTypesToExport = new Map();
95
+ for (const struct of allStructs) {
96
+ const id = Demonomorphizer.buildIdForStruct(struct.structType);
97
+ if (structTypesToExport.has(id)) {
98
+ continue;
99
+ }
100
+ structTypesToExport.set(id, struct.structType);
101
+ }
102
+ // Then, we have to consider the case where we have struct with the same name but different fields.
103
+ // For those, we'll naively append a number to the name.
104
+ const idsPerName = new Map();
105
+ for (const [id, structType] of structTypesToExport.entries()) {
106
+ const name = getLastComponentOfPath(structType.path);
107
+ const ids = idsPerName.get(name) ?? [];
108
+ ids.push(id);
109
+ idsPerName.set(name, ids);
110
+ }
111
+ this.structIdToTsName = new Map();
112
+ for (const [name, ids] of idsPerName.entries()) {
113
+ if (ids.length !== 1) {
114
+ ids.forEach((id, index) => {
115
+ this.structIdToTsName.set(id, `${name}${index + 1}`);
116
+ });
117
+ }
118
+ }
119
+ // Now we can just generate the code for the structs
120
+ let resultCode = '';
121
+ for (const structType of structTypesToExport.values()) {
122
+ resultCode += this.codegenStructType(structType);
123
+ }
124
+ return resultCode;
125
+ }
126
+ getStructName(structType) {
127
+ return (this.structIdToTsName.get(Demonomorphizer.buildIdForStruct(structType)) || getLastComponentOfPath(structType.path));
128
+ }
129
+ codegenStructType(structType) {
130
+ // Generate names for the generic bindings.
131
+ const genericsNameMap = new Map();
132
+ structType.generics.forEach((generic, index) => {
133
+ genericsNameMap.set(generic.id, String.fromCharCode('A'.charCodeAt(0) + index));
134
+ });
135
+ const name = this.getStructName(structType);
136
+ const generics = structType.generics.length
137
+ ? `<${structType.generics
138
+ .map((generic) => `${genericsNameMap.get(generic.id)}${generic.isNumeric ? ' extends number' : ''}`)
139
+ .join(', ')}>`
140
+ : '';
141
+ let resultCode = `export type ${name}${generics} = {\n`;
142
+ for (const field of structType.fields) {
143
+ resultCode += ` ${field.name}: ${this.codegenType(field.type, genericsNameMap)};\n`;
144
+ }
145
+ resultCode += '}\n\n';
146
+ return resultCode;
147
+ }
148
+ codegenType(type, genericsNameMap) {
149
+ switch (type.kind) {
150
+ case 'field':
151
+ this.addIfUnique({ aliasName: 'Field', tsType: 'string' });
152
+ return 'Field';
153
+ case 'boolean':
154
+ return 'boolean';
155
+ case 'integer': {
156
+ const typeName = type.sign === 'signed' ? `i${type.width}` : `u${type.width}`;
157
+ // Even though noir accepts numbers or strings for integers, it always returns strings
158
+ // So we must use string as the type here.
159
+ this.addIfUnique({ aliasName: typeName, tsType: 'string' });
160
+ return typeName;
161
+ }
162
+ case 'binding':
163
+ return genericsNameMap.get(type.id.id) ?? 'unknown';
164
+ case 'constant':
165
+ return type.value.toString();
166
+ case 'string':
167
+ return `string`;
168
+ case 'array':
169
+ if (this.useFixedLengthArrays) {
170
+ if (type.length === null) {
171
+ throw new Error('Got unbounded array with fixed length arrays enabled');
172
+ }
173
+ return `FixedLengthArray<${this.codegenType(type.type, genericsNameMap)}, ${replaceNumericBinding(type.length, genericsNameMap)}>`;
174
+ }
175
+ else {
176
+ return `${this.codegenType(type.type, genericsNameMap)}[]`;
177
+ }
178
+ case 'tuple': {
179
+ const fieldTypes = type.fields.map((field) => this.codegenType(field, genericsNameMap));
180
+ return `[${fieldTypes.join(', ')}]`;
181
+ }
182
+ case 'struct': {
183
+ const name = this.getStructName(type.structType);
184
+ if (type.args.length) {
185
+ const args = type.args.map((arg) => this.codegenType(arg, genericsNameMap)).join(', ');
186
+ return `${name}<${args}>`;
187
+ }
188
+ else {
189
+ return name;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Typescript does not allow us to check for equality of non-primitive types
196
+ * easily, so we create a addIfUnique function that will only add an item
197
+ * to the map if it is not already there by using JSON.stringify.
198
+ * @param item - The item to add to the map.
199
+ */
200
+ addIfUnique(item) {
201
+ const key = JSON.stringify(item);
202
+ if (!this.primitiveTypesUsed.has(key)) {
203
+ this.primitiveTypesUsed.set(key, item);
204
+ }
205
+ }
206
+ /**
207
+ * Codegen all the interfaces for the circuits.
208
+ * For a circuit named Foo, we'll codegen FooInputType and FooReturnType.
209
+ */
210
+ codegenAllInterfaces() {
211
+ let resultCode = '';
212
+ for (const { circuitName, params, returnType, artifact } of this.demonomorphizedAbis) {
213
+ const functionSignature = {
214
+ inputs: params.map((param) => [param.name, this.codegenType(param.type, new Map())]),
215
+ returnValue: returnType ? this.codegenType(returnType, new Map()) : null,
216
+ };
217
+ resultCode += this.codegenStructType({
218
+ path: `${circuitName}InputType`,
219
+ fields: params,
220
+ generics: [],
221
+ });
222
+ if (returnType) {
223
+ resultCode += `export type ${circuitName}ReturnType = ${this.codegenType(returnType, new Map())};\n`;
224
+ }
225
+ resultCode += codegenFunction(circuitName, functionSignature, artifact);
226
+ }
227
+ return resultCode;
228
+ }
229
+ codegenAllPrimitives() {
230
+ let primitiveTypeAliases = this.useFixedLengthArrays
231
+ ? 'export type FixedLengthArray<T, L extends number> = L extends 0 ? never[]: T[] & { length: L }\n'
232
+ : '';
233
+ for (const [, value] of this.primitiveTypesUsed) {
234
+ primitiveTypeAliases += `export type ${value.aliasName} = ${value.tsType};\n`;
235
+ }
236
+ return primitiveTypeAliases;
237
+ }
238
+ }
239
+ const codegenFunction = (name, function_signature, compiled_program) => {
240
+ const args = function_signature.inputs.map(([name]) => `${name}`).join(', ');
241
+ const args_with_types = function_signature.inputs.map(([name, type]) => `${name}: ${type}`).join(', ');
242
+ const artifact = compiled_program
243
+ ? `export const ${name}_circuit: CompiledCircuit = ${JSON.stringify(compiled_program)};`
244
+ : '';
245
+ return `${artifact}
246
+
247
+ export async function ${name}(${args_with_types}${compiled_program ? '' : `, ${name}_circuit: CompiledCircuit`}, foreignCallHandler?: ForeignCallHandler): Promise<${function_signature.returnValue}> {
248
+ const program = new Noir(${name}_circuit);
249
+ const args: InputMap = { ${args} };
250
+ const { returnValue } = await program.execute(args, foreignCallHandler);
251
+ return returnValue as ${function_signature.returnValue};
252
+ }
253
+ `;
254
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "contributors": [
4
4
  "The Noir Team <team@noir-lang.org>"
5
5
  ],
6
- "version": "0.30.0",
6
+ "version": "0.31.0",
7
7
  "packageManager": "yarn@3.5.1",
8
8
  "license": "(MIT OR Apache-2.0)",
9
9
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "url": "https://github.com/noir-lang/noir/issues"
18
18
  },
19
19
  "dependencies": {
20
- "@noir-lang/types": "0.30.0",
20
+ "@noir-lang/types": "0.31.0",
21
21
  "glob": "^10.3.10",
22
22
  "ts-command-line-args": "^2.5.1"
23
23
  },
@@ -46,7 +46,7 @@
46
46
  "clean": "rm -rf ./lib"
47
47
  },
48
48
  "devDependencies": {
49
- "@noir-lang/noir_js": "0.30.0",
49
+ "@noir-lang/noir_js": "0.31.0",
50
50
  "@types/chai": "^4",
51
51
  "@types/mocha": "^10.0.1",
52
52
  "@types/node": "^20.6.2",
@@ -1,40 +0,0 @@
1
- import { AbiType, Abi } from '@noir-lang/noirc_abi';
2
- /**
3
- * Keep track off all of the Noir primitive types that were used.
4
- * Most of these will not have a 1-1 definition in TypeScript,
5
- * so we will need to generate type aliases for them.
6
- *
7
- * We want to generate type aliases
8
- * for specific types that are used in the ABI.
9
- *
10
- * For example:
11
- * - If `Field` is used we want to alias that
12
- * with `number`.
13
- * - If `u32` is used we want to alias that with `number` too.
14
- */
15
- export type PrimitiveTypesUsed = {
16
- /**
17
- * The name of the type alias that we will generate.
18
- */
19
- aliasName: string;
20
- /**
21
- * The TypeScript type that we will alias to.
22
- */
23
- tsType: string;
24
- };
25
- /**
26
- * Generates a TypeScript interface for the ABI.
27
- * @param abiObj - The ABI to generate the interface for.
28
- * @returns The TypeScript code to define the interface.
29
- */
30
- export declare function generateTsInterface(abiObj: Abi, structsEncountered: Map<string, {
31
- name: string;
32
- type: AbiType;
33
- }[]>, primitiveTypeMap: Map<string, PrimitiveTypesUsed>): {
34
- inputs: [string, string][];
35
- returnValue: string | null;
36
- };
37
- export declare function codegenStructDefinitions(structsEncountered: Map<string, {
38
- name: string;
39
- type: AbiType;
40
- }[]>, primitiveTypeMap: Map<string, PrimitiveTypesUsed>): string;
package/lib/noir_types.js DELETED
@@ -1,134 +0,0 @@
1
- /**
2
- * Typescript does not allow us to check for equality of non-primitive types
3
- * easily, so we create a addIfUnique function that will only add an item
4
- * to the map if it is not already there by using JSON.stringify.
5
- * @param item - The item to add to the map.
6
- */
7
- function addIfUnique(primitiveTypeMap, item) {
8
- const key = JSON.stringify(item);
9
- if (!primitiveTypeMap.has(key)) {
10
- primitiveTypeMap.set(key, item);
11
- }
12
- }
13
- /**
14
- * Converts an ABI type to a TypeScript type.
15
- * @param type - The ABI type to convert.
16
- * @returns The typescript code to define the type.
17
- */
18
- function abiTypeToTs(type, primitiveTypeMap) {
19
- switch (type.kind) {
20
- case 'field':
21
- addIfUnique(primitiveTypeMap, { aliasName: 'Field', tsType: 'string' });
22
- return 'Field';
23
- case 'integer': {
24
- const typeName = type.sign === 'signed' ? `i${type.width}` : `u${type.width}`;
25
- // Javascript cannot safely represent the full range of Noir's integer types as numbers.
26
- // `Number.MAX_SAFE_INTEGER == 2**53 - 1` so we disallow passing numbers to types which may exceed this.
27
- // 52 has been chosen as the cutoff rather than 53 for safety.
28
- const tsType = type.width <= 52 ? `string | number` : `string`;
29
- addIfUnique(primitiveTypeMap, { aliasName: typeName, tsType });
30
- return typeName;
31
- }
32
- case 'boolean':
33
- return `boolean`;
34
- case 'array':
35
- // We can't force the usage of fixed length arrays as this currently throws errors in TS.
36
- // The array would need to be `as const` to support this whereas that's unlikely to happen in user code.
37
- // return `FixedLengthArray<${abiTypeToTs(type.type, primitiveTypeMap)}, ${type.length}>`;
38
- return `${abiTypeToTs(type.type, primitiveTypeMap)}[]`;
39
- case 'string':
40
- // We could enforce that literals are the correct length but not generally.
41
- // This would run into similar problems to above.
42
- return `string`;
43
- case 'struct':
44
- return getLastComponentOfPath(type.path);
45
- case 'tuple': {
46
- const field_types = type.fields.map((field) => abiTypeToTs(field, primitiveTypeMap));
47
- return `[${field_types.join(', ')}]`;
48
- }
49
- default:
50
- throw new Error(`Unknown ABI type ${JSON.stringify(type)}`);
51
- }
52
- }
53
- /**
54
- * Returns the last component of a path, e.g. "foo::bar::baz" -\> "baz"
55
- * Note: that if we have a path such as "Baz", we will return "Baz".
56
- *
57
- * Since these paths corresponds to structs, we can assume that we
58
- * cannot have "foo::bar::".
59
- *
60
- * We also make the assumption that since these paths are coming from
61
- * Noir, then we will not have two paths that look like this:
62
- * - foo::bar::Baz
63
- * - cat::dog::Baz
64
- * ie the last component of the path (struct name) is enough to uniquely identify
65
- * the whole path.
66
- *
67
- * TODO: We should double check this assumption when we use type aliases,
68
- * I expect that `foo::bar::Baz as Dog` would effectively give `foo::bar::Dog`
69
- * @param str - The path to get the last component of.
70
- * @returns The last component of the path.
71
- */
72
- function getLastComponentOfPath(str) {
73
- const parts = str.split('::');
74
- const lastPart = parts[parts.length - 1];
75
- return lastPart;
76
- }
77
- /**
78
- * Generates TypeScript interfaces for the structs used in the ABI.
79
- * @param type - The ABI type to generate the interface for.
80
- * @param output - The set of structs that we have already generated bindings for.
81
- * @returns The TypeScript code to define the struct.
82
- */
83
- function generateStructInterfaces(type, structsEncountered, primitiveTypeMap) {
84
- // Edge case to handle the array of structs case.
85
- if (type.kind === 'array' &&
86
- type.type.kind === 'struct' &&
87
- !structsEncountered.has(getLastComponentOfPath(type.type.path))) {
88
- generateStructInterfaces(type.type, structsEncountered, primitiveTypeMap);
89
- }
90
- if (type.kind !== 'struct')
91
- return;
92
- const structName = getLastComponentOfPath(type.path);
93
- if (!structsEncountered.has(structName)) {
94
- for (const field of type.fields) {
95
- generateStructInterfaces(field.type, structsEncountered, primitiveTypeMap);
96
- }
97
- structsEncountered.set(structName, type.fields);
98
- }
99
- }
100
- /**
101
- * Generates a TypeScript interface for the ABI.
102
- * @param abiObj - The ABI to generate the interface for.
103
- * @returns The TypeScript code to define the interface.
104
- */
105
- export function generateTsInterface(abiObj, structsEncountered, primitiveTypeMap) {
106
- // Define structs for composite types
107
- for (const param of abiObj.parameters) {
108
- generateStructInterfaces(param.type, structsEncountered, primitiveTypeMap);
109
- }
110
- // Generating Return type, if it exists
111
- if (abiObj.return_type != null) {
112
- generateStructInterfaces(abiObj.return_type.abi_type, structsEncountered, primitiveTypeMap);
113
- }
114
- return getTsFunctionSignature(abiObj, primitiveTypeMap);
115
- }
116
- export function codegenStructDefinitions(structsEncountered, primitiveTypeMap) {
117
- let codeGeneratedStruct = '';
118
- for (const [structName, structFields] of structsEncountered) {
119
- codeGeneratedStruct += `export type ${structName} = {\n`;
120
- for (const field of structFields) {
121
- codeGeneratedStruct += ` ${field.name}: ${abiTypeToTs(field.type, primitiveTypeMap)};\n`;
122
- }
123
- codeGeneratedStruct += `};\n\n`;
124
- }
125
- return codeGeneratedStruct;
126
- }
127
- function getTsFunctionSignature(abi, primitiveTypeMap) {
128
- const inputs = abi.parameters.map((param) => [
129
- param.name,
130
- abiTypeToTs(param.type, primitiveTypeMap),
131
- ]);
132
- const returnValue = abi.return_type ? abiTypeToTs(abi.return_type.abi_type, primitiveTypeMap) : null;
133
- return { inputs, returnValue };
134
- }