diff --git a/examples/canister/src/index.did b/examples/canister/src/index.did index dc020cd99a..4d7160845f 100644 --- a/examples/canister/src/index.did +++ b/examples/canister/src/index.did @@ -1,8 +1,7 @@ -type rec_0 = record {someCanister:service {query1:() -> (bool) query; update1:() -> (text) }}; service: () -> { - canisterParam: (service {query1:() -> (bool) query; update1:() -> (text) }) -> (service {query1:() -> (bool) query; update1:() -> (text) }) query; - canisterReturnType: () -> (service {query1:() -> (bool) query; update1:() -> (text) }) query; - canisterNestedReturnType: () -> (rec_0); - canisterList: (vec service {query1:() -> (bool) query; update1:() -> (text) }) -> (vec service {query1:() -> (bool) query; update1:() -> (text) }); - canisterCrossCanisterCall: (service {query1:() -> (bool) query; update1:() -> (text) }) -> (text); + canisterCrossCanisterCall: (service {query1: () -> (bool) query; update1: () -> (text) ;}) -> (text) ; + canisterList: (vec service {query1: () -> (bool) query; update1: () -> (text) ;}) -> (vec service {query1: () -> (bool) query; update1: () -> (text) ;}) ; + canisterNestedReturnType: () -> (record {someCanister:service {query1: () -> (bool) query; update1: () -> (text) ;}}) ; + canisterParam: (service {query1: () -> (bool) query; update1: () -> (text) ;}) -> (service {query1: () -> (bool) query; update1: () -> (text) ;}) query; + canisterReturnType: () -> (service {query1: () -> (bool) query; update1: () -> (text) ;}) query; } diff --git a/examples/canister/src/some_canister.did b/examples/canister/src/some_canister.did index f61de65f9f..5352637a43 100644 --- a/examples/canister/src/some_canister.did +++ b/examples/canister/src/some_canister.did @@ -1,4 +1,4 @@ service: () -> { query1: () -> (bool) query; - update1: () -> (text); + update1: () -> (text) ; } diff --git a/src/compiler/generate_candid_and_canister_methods.ts b/src/compiler/generate_candid_and_canister_methods.ts index deb63c621b..54179f049a 100644 --- a/src/compiler/generate_candid_and_canister_methods.ts +++ b/src/compiler/generate_candid_and_canister_methods.ts @@ -34,15 +34,16 @@ export function generateCandidAndCanisterMethods(mainJs: string): { const script = new vm.Script(mainJs); script.runInContext(context); - const candidInfo = (sandbox.exports as any).canisterMethods - .getIDL([]) - .accept(new DidVisitor(), { - ...DEFAULT_VISITOR_DATA, - isFirstService: true - }); + const canisterMethods = (sandbox.exports as any).canisterMethods; + + const candidInfo = canisterMethods.getIDL([]).accept(new DidVisitor(), { + ...DEFAULT_VISITOR_DATA, + isFirstService: true, + systemFuncs: canisterMethods.getSystemFunctionIDLs() + }); return { candid: DidResultToCandidString(candidInfo), - canisterMethods: (sandbox.exports as any).canisterMethods + canisterMethods: canisterMethods }; } diff --git a/src/lib_functional/candid/reference/service.ts b/src/lib_functional/candid/reference/service.ts index a175a37372..f62f4875be 100644 --- a/src/lib_functional/candid/reference/service.ts +++ b/src/lib_functional/candid/reference/service.ts @@ -214,11 +214,19 @@ export function Canister( returnFunction.queries = queries; returnFunction.updates = updates; returnFunction.callbacks = callbacks; - returnFunction.getIDL = (parents: Parent[]): IDL.ServiceClass => { + (returnFunction.getSystemFunctionIDLs = ( + parents: Parent[] + ): IDL.FuncClass[] => { const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; - const record = Object.entries(serviceFunctionInfo).reduce( - (accumulator, [methodName, functionInfo]) => { + return Object.entries(serviceFunctionInfo).reduce( + (accumulator, [_methodName, functionInfo]) => { + const mode = functionInfo(parentOrUndefined).mode; + if (mode === 'update' || mode === 'query') { + // We don't want init, post upgrade, etc showing up in the idl + return accumulator; + } + const paramRealIdls = toParamIDLTypes( functionInfo(parentOrUndefined).paramsIdls, parents @@ -227,23 +235,55 @@ export function Canister( functionInfo(parentOrUndefined).returnIdl, parents ); - - const annotations = [functionInfo(parentOrUndefined).mode]; - - return { + return [ ...accumulator, - [methodName]: IDL.Func( - paramRealIdls, - returnRealIdl, - annotations - ) - }; + IDL.Func(paramRealIdls, returnRealIdl, [mode]) + ]; }, - {} as Record + [] as IDL.FuncClass[] ); + }), + (returnFunction.getIDL = (parents: Parent[]): IDL.ServiceClass => { + const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; - return IDL.Service(record); - }; + const record = Object.entries(serviceFunctionInfo).reduce( + (accumulator, [methodName, functionInfo]) => { + const paramRealIdls = toParamIDLTypes( + functionInfo(parentOrUndefined).paramsIdls, + parents + ); + const returnRealIdl = toReturnIDLType( + functionInfo(parentOrUndefined).returnIdl, + parents + ); + + const mode = functionInfo(parentOrUndefined).mode; + let annotations: string[] = []; + if (mode === 'update') { + // do nothing + console.log('This is an update'); + console.log(methodName); + } else if (mode === 'query') { + annotations = ['query']; + } else { + // We don't want init, post upgrade, etc showing up in the idl + return accumulator; + } + + return { + ...accumulator, + [methodName]: IDL.Func( + paramRealIdls, + returnRealIdl, + annotations + ) + }; + }, + {} as Record + ); + + return IDL.Service(record); + }); if (originalPrincipal !== undefined && originalPrincipal._isPrincipal) { return returnFunction(originalPrincipal); diff --git a/src/lib_new/utils.ts b/src/lib_new/utils.ts index 343e1d32fd..2255869811 100644 --- a/src/lib_new/utils.ts +++ b/src/lib_new/utils.ts @@ -89,6 +89,9 @@ export function toIDLType(idl: CandidClass, parents: Parent[]): IDL.Type { } return idl.getIDL(parents); } + if (idl._azleIsCanister) { + return toIDLType(idl(), parents); + } // if (idl.display === undefined || idl.getIDL === undefined) { // throw Error(`${JSON.stringify(idl)} is not a candid type`); // } diff --git a/src/lib_new/visitors/did_visitor.ts b/src/lib_new/visitors/did_visitor.ts index bc6f877167..2d7f2c291a 100644 --- a/src/lib_new/visitors/did_visitor.ts +++ b/src/lib_new/visitors/did_visitor.ts @@ -4,6 +4,7 @@ type VisitorData = { usedRecClasses: IDL.RecClass[]; isOnService: boolean; isFirstService: boolean; + systemFuncs: IDL.FuncClass[]; }; type VisitorResult = [CandidDef, CandidTypesDefs]; @@ -17,7 +18,8 @@ type VisitorResult = [CandidDef, CandidTypesDefs]; export const DEFAULT_VISITOR_DATA: VisitorData = { usedRecClasses: [], isOnService: false, - isFirstService: false + isFirstService: false, + systemFuncs: [] }; export function DidResultToCandidString(result: VisitorResult): string { @@ -56,6 +58,13 @@ export function extractCandid( return [paramCandid, candidTypeDefs]; } +function hch(value: any) { + if (value._azleIsCanister) { + return value().getIDL(); + } + return value; +} + export class DidVisitor extends IDL.Visitor { visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult { // To get all of the candid types we need to look at all of the methods @@ -71,14 +80,19 @@ export class DidVisitor extends IDL.Visitor { t._fields .filter(([_name, func]) => isOtherFunction(func)) .map(([_name, func]) => - func.accept(this, { ...data, isOnService: true }) + func.accept(this, { + ...data, + isOnService: true, + isFirstService: false + }) ) )[1]; const isQueryOrUpdateFunction = (func: IDL.FuncClass) => { return ( func.annotations.includes('update') || - func.annotations.includes('query') + func.annotations.includes('query') || + func.annotations.length === 0 //updates have not annotations ); }; // To get all of the canister methods we need to only look at update and query @@ -86,7 +100,11 @@ export class DidVisitor extends IDL.Visitor { t._fields .filter(([_name, func]) => isQueryOrUpdateFunction(func)) .map(([_name, func]) => - func.accept(this, { ...data, isOnService: true }) + func.accept(this, { + ...data, + isOnService: true, + isFirstService: false + }) ) ); const canisterMethodsNames = t._fields @@ -99,17 +117,25 @@ export class DidVisitor extends IDL.Visitor { func.annotations.includes('postUpgrade'); // To get the service params we need to look at the init function const initMethod = extractCandid( - t._fields - .filter(([_name, func]) => isInitFunction(func)) - .map(([_name, initFunc]) => - initFunc.accept(this, { ...data, isOnService: true }) + data.systemFuncs + .filter((func) => isInitFunction(func)) + .map((initFunc) => + initFunc.accept(this, { + ...data, + isOnService: true, + isFirstService: false + }) ) ); const postMethod = extractCandid( - t._fields - .filter(([_name, func]) => isPostUpgradeFunction(func)) - .map(([_name, initFunc]) => - initFunc.accept(this, { ...data, isOnService: true }) + data.systemFuncs + .filter((func) => isPostUpgradeFunction(func)) + .map((initFunc) => + initFunc.accept(this, { + ...data, + isOnService: true, + isFirstService: false + }) ) ); const initMethodCandidString = initMethod[0]; @@ -144,18 +170,21 @@ export class DidVisitor extends IDL.Visitor { ...postMethod[1] }; + const tab = data.isFirstService ? ' ' : ''; + const func_separator = data.isFirstService ? '\n' : ' '; + const funcStrings = canisterMethods[0] .map((value, index) => { - return ` ${canisterMethodsNames[index]}: ${value};`; + return `${tab}${canisterMethodsNames[index]}: ${value};`; }) - .join('\n'); + .join(func_separator); if (data.isFirstService) { return [ `service: ${canisterParamsString} -> {\n${funcStrings}\n}`, candidTypes ]; } - return [`service {\n${funcStrings}\n}`, candidTypes]; + return [`service {${funcStrings}}`, candidTypes]; } visitPrimitive( t: IDL.PrimitiveType, @@ -169,7 +198,7 @@ export class DidVisitor extends IDL.Visitor { data: VisitorData ): VisitorResult { const fields = components.map((value) => - value.accept(this, { ...data, isOnService: false }) + hch(value).accept(this, { ...data, isOnService: false }) ); const candid = extractCandid(fields); return [`record {${candid[0].join('; ')}}`, candid[1]]; @@ -179,7 +208,7 @@ export class DidVisitor extends IDL.Visitor { ty: IDL.Type, data: VisitorData ): VisitorResult { - const candid = ty.accept(this, { ...data, isOnService: false }); + const candid = hch(ty).accept(this, { ...data, isOnService: false }); return [`opt ${candid[0]}`, candid[1]]; } visitVec( @@ -187,16 +216,16 @@ export class DidVisitor extends IDL.Visitor { ty: IDL.Type, data: VisitorData ): VisitorResult { - const candid = ty.accept(this, { ...data, isOnService: false }); + const candid = hch(ty).accept(this, { ...data, isOnService: false }); return [`vec ${candid[0]}`, candid[1]]; } visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult { const argsTypes = t.argTypes.map((value) => - value.accept(this, { ...data, isOnService: false }) + hch(value).accept(this, { ...data, isOnService: false }) ); const candidArgs = extractCandid(argsTypes); const retsTypes = t.retTypes.map((value) => - value.accept(this, { ...data, isOnService: false }) + hch(value).accept(this, { ...data, isOnService: false }) ); const candidRets = extractCandid(retsTypes); const args = candidArgs[0].join(', '); @@ -220,7 +249,7 @@ export class DidVisitor extends IDL.Visitor { // Everything else will just be the normal inline candid def const usedRecClasses = data.usedRecClasses; if (!usedRecClasses.includes(t)) { - const candid = ty.accept(this, { + const candid = hch(ty).accept(this, { usedRecClasses: [...usedRecClasses, t], isOnService: false, isFirstService: false @@ -237,7 +266,7 @@ export class DidVisitor extends IDL.Visitor { data: VisitorData ): VisitorResult { const candidFields = fields.map(([key, value]) => - value.accept(this, { ...data, isOnService: false }) + hch(value).accept(this, { ...data, isOnService: false }) ); const candid = extractCandid(candidFields); const field_strings = fields.map( @@ -251,7 +280,7 @@ export class DidVisitor extends IDL.Visitor { data: VisitorData ): VisitorResult { const candidFields = fields.map(([key, value]) => - value.accept(this, { ...data, isOnService: false }) + hch(value).accept(this, { ...data, isOnService: false }) ); const candid = extractCandid(candidFields); const fields_string = fields.map( diff --git a/src/lib_new/visitors/encode_decode/index.ts b/src/lib_new/visitors/encode_decode/index.ts index b2119a5dd9..bda98a20a3 100644 --- a/src/lib_new/visitors/encode_decode/index.ts +++ b/src/lib_new/visitors/encode_decode/index.ts @@ -22,13 +22,20 @@ export type VisitorResult = any; * is extracted into these helper methods. */ +function hch(value: any) { + if (value._azleIsCanister) { + return value().getIDL(); + } + return value; +} + export function visitTuple( visitor: DecodeVisitor | EncodeVisitor, components: IDL.Type[], data: VisitorData ): VisitorResult { const fields = components.map((value, index) => - value.accept(visitor, { + hch(value).accept(visitor, { js_data: data.js_data[index], js_class: data.js_class._azleTypes[index] }) @@ -44,7 +51,7 @@ export function visitOpt( if (data.js_data.length === 0) { return data.js_data; } - const candid = ty.accept(visitor, { + const candid = hch(ty).accept(visitor, { js_data: data.js_data[0], js_class: data.js_class._azleType }); @@ -60,7 +67,7 @@ export function visitVec( return data.js_data; } return data.js_data.map((array_elem: any) => { - return ty.accept(visitor, { + return hch(ty).accept(visitor, { js_data: array_elem, js_class: data.js_class._azleType }); @@ -78,7 +85,7 @@ export function visitRecord( return { ...acc, - [memberName]: memberIdl.accept(visitor, { + [memberName]: hch(memberIdl).accept(visitor, { js_data: fieldData, js_class: fieldClass }) @@ -100,7 +107,7 @@ export function visitVariant( const okClass = data.js_class._azleOk; return Result.Ok( - okField[1].accept(visitor, { + hch(okField[1]).accept(visitor, { js_data: okData, js_class: okClass }) @@ -111,7 +118,7 @@ export function visitVariant( const errData = data.js_data['Err']; const errClass = data.js_class._azleErr; return Result.Err( - errField[1].accept(visitor, { + hch(errField[1]).accept(visitor, { js_data: errData, js_class: errClass }) @@ -127,7 +134,7 @@ export function visitVariant( } return { ...acc, - [memberName]: memberIdl.accept(visitor, { + [memberName]: hch(memberIdl).accept(visitor, { js_class: fieldClass, js_data: fieldData }) @@ -146,7 +153,7 @@ export function visitRec( if (js_class._azleIsCanister) { js_class = js_class([]); } - return ty.accept(visitor, { + return hch(ty).accept(visitor, { ...data, js_class });