Skip to content

Commit 8d3718c

Browse files
author
nicosammito
committed
feat: enhance flow source code generation to support inference and improve node call handling
1 parent ba62db8 commit 8d3718c

1 file changed

Lines changed: 72 additions & 13 deletions

File tree

src/utils.ts

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
} from "@code0-tech/sagittarius-graphql-types";
1111
import ts from "typescript";
1212
import {createSystem, createVirtualTypeScriptEnvironment, VirtualTypeScriptEnvironment} from "@typescript/vfs"
13+
import {getTypesFromNode} from "./extraction/getTypesFromNode";
14+
import {DataTypeVariant, getTypeVariant} from "./extraction/getTypeVariant";
1315

1416
/**
1517
* Result of a node or flow validation.
@@ -130,12 +132,47 @@ export const sanitizeId = (id: string) => id.replace(/[^a-zA-Z0-9]/g, '_');
130132
export function generateFlowSourceCode(
131133
flow?: Flow,
132134
functions?: FunctionDefinition[],
133-
dataTypes?: DataType[]
135+
dataTypes?: DataType[],
136+
isForInference: boolean = false
134137
): string {
135138
const nodes = flow?.nodes?.nodes || [];
136139
const funcMap = new Map(functions?.map(f => [f.identifier, f]));
137140
const visited = new Set<string>();
138141

142+
const generateNodeCall = (nodeId: string, parentNodeId?: string, parentParamIndex?: number): string => {
143+
const node = nodes.find(n => n?.id === nodeId);
144+
if (!node || !node.functionDefinition?.identifier) return "undefined";
145+
146+
const params = (node.parameters?.nodes as NodeParameter[]) || [];
147+
const args = params.map((p, paramIdx) => {
148+
const val = p.value;
149+
if (!val) return `/* @pos ${nodeId} ${paramIdx} */ undefined`;
150+
if (val.__typename === "ReferenceValue") {
151+
const ref = val as ReferenceValue;
152+
if (!ref.nodeFunctionId) return `/* @pos ${nodeId} ${paramIdx} */ undefined`;
153+
let refCode = ref.inputIndex !== undefined
154+
? `p_${sanitizeId(ref.nodeFunctionId)}[${ref.inputIndex}]`
155+
: `node_${sanitizeId(ref.nodeFunctionId)}`;
156+
ref.referencePath?.forEach(pathObj => { refCode += `?.${pathObj.path}`; });
157+
return `/* @pos ${nodeId} ${paramIdx} */ ${refCode}`;
158+
}
159+
if (val.__typename === "LiteralValue") return `/* @pos ${nodeId} ${paramIdx} */ ${JSON.stringify(val.value)}`;
160+
if (val.__typename === "NodeFunctionIdWrapper") {
161+
const wrapper = val as NodeFunctionIdWrapper;
162+
return generateNodeCall(wrapper.id!, nodeId, paramIdx);
163+
}
164+
return `/* @pos ${nodeId} ${paramIdx} */ undefined`;
165+
}).join(", ");
166+
167+
const funcName = `fn_${node.functionDefinition.identifier.replace(/::/g, '_')}`;
168+
const call = `${funcName}(${args})`;
169+
// Add position comment only for nested calls (when called from within an argument)
170+
if (parentNodeId !== undefined && parentParamIndex !== undefined) {
171+
return `/* @pos ${nodeId} 0 */ ${call}`;
172+
}
173+
return call;
174+
};
175+
139176
const generateNodeCode = (nodeId: string, indent: string = ""): string => {
140177
if (visited.has(nodeId)) return "";
141178
const node = nodes.find(n => n?.id === nodeId);
@@ -145,34 +182,57 @@ export function generateFlowSourceCode(
145182
const funcDef = funcMap.get(node.functionDefinition.identifier);
146183
if (!funcDef) return `${indent}// Error: Function ${node.functionDefinition.identifier} not found\n`;
147184

185+
// Only use getTypesFromNode if we are NOT already doing inference to avoid infinite recursion
186+
let nodeTypes: any = { parameters: [] };
187+
if (!isForInference) {
188+
nodeTypes = getTypesFromNode(node, functions, dataTypes);
189+
}
190+
148191
const params = (node.parameters?.nodes as NodeParameter[]) || [];
149192
const args = params.map((p, index) => {
150193
const val = p.value;
151-
if (!val) return "undefined";
194+
if (!val) return `/* @pos ${nodeId} ${index} */ undefined`;
152195
if (val.__typename === "ReferenceValue") {
153196
const ref = val as ReferenceValue;
154-
if (!ref.nodeFunctionId) return "undefined";
155-
let refCode = ref.parameterIndex !== undefined
156-
? `p_${sanitizeId(ref.nodeFunctionId)}_${ref.parameterIndex}`
197+
if (!ref.nodeFunctionId) return `/* @pos ${nodeId} ${index} */ undefined`;
198+
let refCode = ref.inputIndex !== undefined
199+
? `p_${sanitizeId(ref.nodeFunctionId)}[${ref.inputIndex}]`
157200
: `node_${sanitizeId(ref.nodeFunctionId)}`;
158201
ref.referencePath?.forEach(pathObj => { refCode += `?.${pathObj.path}`; });
159202
return `/* @pos ${nodeId} ${index} */ ${refCode}`;
160203
}
161204
if (val.__typename === "LiteralValue") return `/* @pos ${nodeId} ${index} */ ${JSON.stringify(val.value)}`;
162205
if (val.__typename === "NodeFunctionIdWrapper") {
163206
const wrapper = val as NodeFunctionIdWrapper;
164-
const lambdaArgName = `p_${sanitizeId(nodeId)}_${index}`;
165-
const subTreeCode = generateNodeCode(wrapper.id!, indent + " ");
166-
return `/* @pos ${nodeId} ${index} */ (${lambdaArgName}) => {\n${subTreeCode}${indent}}`;
207+
208+
if (!isForInference) {
209+
const expectedType = nodeTypes.parameters[index];
210+
const isFunctionType = expectedType ? getTypeVariant(expectedType, dataTypes) === DataTypeVariant.NODE : false;
211+
212+
if (isFunctionType) {
213+
const lambdaArgName = `p_${sanitizeId(nodeId)}`;
214+
const subTreeCode = generateNodeCode(wrapper.id!, indent + " ");
215+
return `/* @pos ${nodeId} ${index} */ (...${lambdaArgName}) => {\n${subTreeCode}${indent}}`;
216+
} else {
217+
const nestedCall = generateNodeCall(wrapper.id!, nodeId, index);
218+
return `/* @pos ${nodeId} ${index} */ ${nestedCall}`;
219+
}
220+
} else {
221+
// During inference, we just need something valid.
222+
// Defaulting to a lambda is safer for type inference of the parent node's parameters.
223+
const lambdaArgName = `p_${sanitizeId(nodeId)}`;
224+
const subTreeCode = generateNodeCode(wrapper.id!, indent + " ");
225+
return `/* @pos ${nodeId} ${index} */ (...${lambdaArgName}) => {\n${subTreeCode}${indent}}`;
226+
}
167227
}
168-
return "undefined";
228+
return `/* @pos ${nodeId} ${index} */ undefined`;
169229
}).join(", ");
170230

171231
const varName = `node_${sanitizeId(node.id!)}`;
172-
const funcName = `fn_${node.functionDefinition.identifier.replace(/::/g, '_')}`;
232+
const funcName = `fn_${node?.functionDefinition?.identifier?.replace(/::/g, '_')}`;
173233
const needsAnyCast = args.includes("undefined");
174234
const isReturnNode = node.functionDefinition.identifier === "std::control::return";
175-
let code = `${indent}${isReturnNode ? "return " : `var ${varName} = `}${funcName}(${args})${needsAnyCast ? " as any" : ""} ;\n`;
235+
let code = `${indent}${isReturnNode ? "return " : `var ${varName} = `}${funcName}(${args})${needsAnyCast ? "" : ""} ;\n`;
176236
if (node.nextNodeId) code += generateNodeCode(node.nextNodeId, indent);
177237
return code;
178238
};
@@ -207,7 +267,7 @@ export function getInferredTypesFromFlow(
207267
functions?: FunctionDefinition[],
208268
dataTypes?: DataType[]
209269
): InferredTypes {
210-
const sourceCode = generateFlowSourceCode(flow, functions, dataTypes);
270+
const sourceCode = generateFlowSourceCode(flow, functions, dataTypes, true);
211271
const fileName = "index.ts";
212272
const host = createCompilerHost(fileName, sourceCode);
213273
const sourceFile = host.getSourceFile(fileName)!;
@@ -249,4 +309,3 @@ export function getInferredTypesFromFlow(
249309

250310
return { nodes: nodeTypes, parameters: parameterTypes };
251311
}
252-

0 commit comments

Comments
 (0)