@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
150 lines • 8.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.markAsOnlyBuiltIn = markAsOnlyBuiltIn;
exports.processNamedCall = processNamedCall;
const info_1 = require("../../../../info");
const known_call_handling_1 = require("./known-call-handling");
const append_1 = require("../../../../environments/append");
const node_id_1 = require("../../../../../r-bridge/lang-4.x/ast/model/processing/node-id");
const identifier_1 = require("../../../../environments/identifier");
const r_base_packages_1 = require("../../../../../util/r-base-packages");
const built_in_1 = require("../../../../environments/built-in");
const edge_1 = require("../../../../graph/edge");
const built_in_library_1 = require("./built-in/built-in-library");
const vertex_1 = require("../../../../graph/vertex");
const resolve_helper_1 = require("../../../../environments/resolve-helper");
const assert_1 = require("../../../../../util/assert");
function mergeInformation(info, newInfo) {
if (info === undefined) {
return newInfo;
}
return {
unknownReferences: info.unknownReferences.concat(newInfo.unknownReferences),
in: info.in.concat(newInfo.in),
out: info.out.concat(newInfo.out),
graph: info.graph.mergeWith(newInfo.graph),
environment: (0, append_1.appendEnvironment)(info.environment, newInfo.environment),
entryPoint: newInfo.entryPoint,
exitPoints: info.exitPoints.concat(newInfo.exitPoints),
hooks: info.hooks.concat(newInfo.hooks)
};
}
/**
* The flowR definition of `pkg::fn` behind an export a signature database attached. Such an export binds the
* name in a layer below the global environment, which hides the built-in environment underneath, and with it
* how the call evaluates its arguments: without this lookup `library(dplyr); filter(df, id > 2)` loses the data
* mask of `dplyr::filter`. Only that is recovered here. What a definition merely states about a call (its
* properties, its signature) reaches every consumer through the export vertex already, so a definition saying
* nothing about evaluation is left to the default processor, which keeps the call to one target.
*/
function builtInBehindExport(data, definition) {
if (definition.type !== identifier_1.ReferenceType.Function || definition.name === undefined || !node_id_1.NodeId.isBuiltIn(definition.nodeId)
|| identifier_1.Identifier.getNamespace(definition.name) === undefined) {
return undefined;
}
const known = data.ctx.env.builtInFunctionOf(definition.name);
return (0, built_in_1.statesNonStandardEvaluation)(known) ? known : undefined;
}
/**
* Marks the given function call node as only calling built-in functions.
*/
function markAsOnlyBuiltIn(graph, rootId, keepEnvironment = false) {
const v = graph.getVertex(rootId);
if (vertex_1.FunctionCallVertex.is(v)) {
v.onlyBuiltin = true;
if (!keepEnvironment) {
v.environment = undefined;
}
}
}
/**
* Processes a named function call within the dataflow analysis.
* For example, `myFunction(arg1, arg2)`, resolves against the environment.
*/
function processNamedCall(name, args, rootId, data) {
const resolved = resolve_helper_1.Resolve.byNameAndType(name.content, data.environment, identifier_1.ReferenceType.Function) ?? [];
let defaultProcessor = resolved.length === 0;
/* a call whose meaning a later pass looks up in its environment has to keep it */
const keepEnvironment = resolved.some(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.config?.keepEnvironment === true);
/* if this call will be marked as built-in only (see below), its vertex needs no environment snapshot */
if (resolved.length > 0 && !keepEnvironment
&& resolved.every(r => r.type === identifier_1.ReferenceType.BuiltInFunction && typeof r.processor === 'function')
&& resolved.some(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.config?.libFn !== true)) {
data = { ...data, builtInNoEnv: rootId };
}
let information = undefined;
let builtIn = false;
/* a set, because an export and the built-in behind it are two ways to the same definition */
const toRun = new Set();
for (const resolvedFunction of resolved) {
const own = resolvedFunction.type === identifier_1.ReferenceType.BuiltInFunction && typeof resolvedFunction.processor === 'function'
? resolvedFunction
/* the call goes through the attached export, so it is not built-in only, but flowR's own
definition of that export is what knows how to process it */
: builtInBehindExport(data, resolvedFunction);
if (own === undefined) {
defaultProcessor = true;
continue;
}
builtIn ||= own === resolvedFunction && own.config?.libFn !== true;
toRun.add(own);
}
for (const own of toRun) {
information = mergeInformation(information, own.processor(name, args, rootId, data));
}
if (defaultProcessor) {
/* if we do not know where we land, we force! reuse `resolved`, data.environment did not change above */
const call = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, forceArgs: resolved.length > 0 ? undefined : 'all', origin: 'default' });
information = mergeInformation(information, call.information);
}
else if (information && builtIn) {
// mark the function call as built in only
markAsOnlyBuiltIn(information.graph, rootId, keepEnvironment);
}
// on demand: materialize the built-in vertex for any package export this call resolves to and, when we
// already know the loading call here, link to it (a function-local export is invisible to the later
// top-level linkMaterializedExportsToLoaders pass, so we must catch it at the call site)
if (information) {
const sigdb = data.ctx.config.solver.sigdb;
for (const r of resolved) {
if (r.type === identifier_1.ReferenceType.Function && r.nodeId !== undefined && node_id_1.NodeId.isBuiltIn(r.nodeId)) {
(0, built_in_library_1.attachExportVertex)(information.graph, r.nodeId, data.environment, data.ctx, data.cds);
const definedAt = r.definedAt;
if (definedAt !== undefined && !node_id_1.NodeId.isBuiltIn(definedAt)) {
information.graph.addEdge(r.nodeId, definedAt, edge_1.EdgeType.Reads | edge_1.EdgeType.Calls);
// the call itself reads the library() load that made this export resolvable
information.graph.addEdge(rootId, definedAt, edge_1.EdgeType.Reads);
}
// opt-in: a lightweight ref edge from the call to its sigdb-backed package function vertex
if (sigdb.linkPackageCalls && node_id_1.NodeId.toPkgFn(r.nodeId) !== undefined) {
information.graph.addEdge(rootId, r.nodeId, edge_1.EdgeType.Reads);
}
}
}
const ns = identifier_1.Identifier.getNamespace(name.content);
// a call links to a database-unresolved `library(pkg)` recorded below the global env: `pkg::fn` names
// its package, a bare call takes it from what flowR resolved it to, which is what ties
// `filter(df, id > 2)` to the `library(dplyr)` that made it dplyr's
const owners = ns !== undefined ? [ns] : resolved
.map(r => r.type === identifier_1.ReferenceType.BuiltInFunction && r.name !== undefined ? identifier_1.Identifier.getNamespace(r.name) : undefined)
.filter(assert_1.isNotUndefined);
for (const owner of owners) {
for (const loadNode of (0, built_in_library_1.loadNodesForNamespace)(data.environment, String(owner))) {
information.graph.addEdge(rootId, loadNode, edge_1.EdgeType.Reads);
}
}
// opt-in: link a bare base-R call to its sigdb function vertex (base-R qualification is edge-free otherwise);
// same guards as Identifier.toQualified -- not namespaced, not resolving to a user definition
if (sigdb.linkBaseRCalls && ns === undefined && !resolved.some(r => r.nodeId !== undefined && !node_id_1.NodeId.isBuiltIn(r.nodeId))) {
const bare = identifier_1.Identifier.getName(name.content);
const owner = (0, r_base_packages_1.baseRExportOwner)(bare);
if (owner !== undefined) {
const builtInId = node_id_1.NodeId.fromPkgFn(owner, bare);
(0, built_in_library_1.attachExportVertex)(information.graph, builtInId, data.environment, data.ctx, data.cds);
information.graph.addEdge(rootId, builtInId, edge_1.EdgeType.Reads);
}
}
}
return information ?? info_1.DataflowInformation.initialize(rootId, data);
}
//# sourceMappingURL=named-call-handling.js.map