diff --git a/lang/ast/structs.go b/lang/ast/structs.go index c0775eb3be..bbeeb37c9c 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -8191,6 +8191,39 @@ func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter return nil, nil, errwrap.Wrapf(err, "could not get the type of the function") } + // Loop over the arguments, add them to the graph, but do _not_ connect them + // to the function vertex. Instead, each time we need to generate a sub-graph + // (perhaps only once if the function expression is constant), the FuncValue + // (either that constant expression or a FuncValue which CallFunc receives + // from upstream) creates the corresponding subgraph and connects these + // arguments to it. + var argFuncs []interfaces.Func + for i, arg := range obj.Args { + argGraph, argFunc, err := arg.Graph(env) + if err != nil { + return nil, nil, errwrap.Wrapf(err, "could not get graph for arg %d", i) + } + graph.AddGraph(argGraph) + argFuncs = append(argFuncs, argFunc) + } + + // if obj.expr is a CallableExpr and that expression is constant, then we + // don't need CallFunc because the same sub-graph will be used for the + // entirety of the execution. Instead, embed that sub-graph in the main graph. + staticValue, err := obj.expr.Value() + if staticValue != nil { + if fullFuncValue, isFuncValue := staticValue.(*full.FuncValue); isFuncValue { + // The function is constant, so we can embed the sub-graph directly. + fakeTxn := panic("TODO: create a txn which adds nodes to the graph") + outputFunc, err := fullFuncValue.Call(fakeTxn, argFuncs) + if err != nil { + return nil, nil, errwrap.Wrapf(err, "could not call the function") + } + graph.AddGraph(fakeTxn.Graph()) + return graph, outputFunc, nil + } + } + // Find the vertex which produces the FuncValue. var funcValueFunc interfaces.Func if _, isParam := obj.expr.(*ExprParam); isParam { @@ -8215,20 +8248,6 @@ func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter funcValueFunc = topLevelFunc } - // Loop over the arguments, add them to the graph, but do _not_ connect them - // to the function vertex. Instead, each time the call vertex (which we - // create below) receives a FuncValue from the function node, it creates the - // corresponding subgraph and connects these arguments to it. - var argFuncs []interfaces.Func - for i, arg := range obj.Args { - argGraph, argFunc, err := arg.Graph(env) - if err != nil { - return nil, nil, errwrap.Wrapf(err, "could not get graph for arg %d", i) - } - graph.AddGraph(argGraph) - argFuncs = append(argFuncs, argFunc) - } - // Add a vertex for the call itself. edgeName := structs.CallFuncArgNameFunction callFunc := &structs.CallFunc{