@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
130 lines (129 loc) • 7.09 kB
TypeScript
import { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { InputTraceType, InputType } from './input-types';
export { InputTraceType, InputType } from './input-types';
import type { DataflowGraph } from '../../../dataflow/graph/graph';
import type { MergeableRecord } from '../../../util/objects';
import { Identifier } from '../../../dataflow/environments/identifier';
import type { FlowrSearchLike } from '../../../search/flowr-search-builder';
/**
* An object that a framework hands to its users without any visible definition, like the `input` of a
* shiny server function. Reads of such an object (and of its fields) are classified as its given type,
* so traces link up to the framework instead of stopping at an opaque parameter.
*/
export interface LinkedInputObject {
/** the name of the object, e.g. `input` */
readonly name: string;
/** how reads of the object (or of its fields) are to be classified */
readonly type: InputType;
/**
* If given, the object only counts as linked if the function binding it declares all of these parameters as well
* (e.g., shiny's `function(input, output, session)`). Without this, every `input` would be treated as the framework's.
*/
readonly withParams?: readonly string[];
/** the package that has to be attached for this object to exist, e.g. `shiny` */
readonly requires?: string;
/** if given, only these fields are inputs (`session$clientData` is, `session$userData` is not) and the object itself is none */
readonly fields?: readonly string[];
/** how the framework declares the entries of this object, so a read of `input$n` links to the `textInput("n", …)` defining it */
readonly declaredBy?: LinkedInputDeclaration;
}
/** The calls that declare the entries of a {@link LinkedInputObject}, and where they carry the entry's name. */
export interface LinkedInputDeclaration {
/** the declaring calls, e.g. shiny's `textInput`, `selectInput`, … */
readonly calls: readonly Identifier[];
/** the name of the argument holding the entry's name */
readonly argName: string;
/** the index of that argument when it is passed positionally */
readonly argIdx: number;
}
/**
* A call a framework is given a function through, binding its parameters *by position* - which is how R passes
* them, so this catches a `shinyApp(ui, function(i, o, s))` that no name-based rule can.
*/
export interface LinkedInputEntryPoint {
/** the call taking the function, e.g. `shiny::shinyApp` */
readonly call: Identifier;
/** the name of the argument holding the function */
readonly argName: string;
/** the index of that argument when it is passed positionally */
readonly argIdx: number;
/** which {@link LinkedInputObject} the framework binds to each parameter, by position; `undefined` leaves one alone */
readonly params: readonly (string | undefined)[];
}
/** A function whose result is bounded by one argument (e.g. `match.arg` by its `choices`), classified by that argument alone. */
export interface NarrowingFunction {
readonly call: Identifier;
/** the name of the bounding argument; omit (with `argIdx`) for a result that is always a bounded, content-independent value */
readonly argName?: string;
/** the index of the bounding argument; omit (with `argName`) for an always-`DerivedConstant` result */
readonly argIdx?: number;
}
/**
* Scalar R constant values representable in TypeScript.
* `null` corresponds to R's `NULL`.
* NA values are not included (they have no direct TS equivalent).
*/
export type ConstantValue = string | number | boolean | null;
/**
* Object attached to an input source
* @see {@link InputSources}
*/
export interface InputSource extends MergeableRecord {
id: NodeId;
types: InputType[];
trace: InputTraceType;
/** if the trace is affected by control dependencies, they are classified too, this is a duplicate free array */
cds?: InputType[];
/**
* Argument name when this source originates from a named argument of the criterion function call,
* or the accessed field when it originates from a {@link LinkedInputObject} (e.g. `n` for `input$n`).
*/
name?: string;
/** the concrete scalar value when the source is a constant or a pure alias of one */
value?: ConstantValue;
/** where the framework entry this source reads is declared, e.g. the `textInput("n", …)` behind an `input$n` */
declaredAt?: NodeId[];
}
/**
* Map of input sources, keyed by the node id of the input source. Each input source is classified with an {@link InputSource} object.
*/
export type InputSources = InputSource[];
/**
* This is either an {@link NodeId|id} of a known functions all of that category (e.g., you can issue a dependencies query before and then pass all
* identified ids to this query here).
*/
export type InputClassifierFunctionIdentifiers = readonly (Identifier | NodeId)[];
/**
* For the specifications of `pure` etc. please have a look at {@link InputClassifierFunctionIdentifiers}.
*/
export interface InputClassifierConfig<Functions extends InputClassifierFunctionIdentifiers | FlowrSearchLike = readonly Identifier[] | FlowrSearchLike> extends Partial<Record<InputType, Functions>> {
/**
* Functions which are considered to be pure (i.e., deterministic, trusted, safe, idempotent on the lub of the input types)
*/
[InputTraceType.Pure]?: Functions;
/**
* Objects provided by a framework rather than by the code itself, like shiny's `input`.
* @see {@link LinkedInputObject}
*/
linkedObjects?: readonly LinkedInputObject[];
/**
* Calls that hand a function to a framework, which then binds {@link linkedObjects} to its parameters by position.
* @see {@link LinkedInputEntryPoint}
*/
linkedEntryPoints?: readonly LinkedInputEntryPoint[];
/**
* Functions whose result is bounded by one of their arguments (classified by that argument alone).
* @see {@link NarrowingFunction}
*/
narrowing?: readonly NarrowingFunction[];
}
/**
* Takes the given id which is expected to either be:
* - a function call - in this case all arguments are considered to be inputs (additionally to all read edges from the function call in the dataflow graph)
* - anything else - in that case the node itself is considered as an "input" - please note that in these scenarios the *return* value will only contain one mapping - that for the id you passed in.
*
* This method traces the dependencies in the dataflow graph using the specification of functions passed in.
* For the scope escape analysis, pass on the full, non-reduced DFG as `fullDfg`, and the packages attached in the
* program as `packages` so that bare calls only match the entries of packages that are actually in scope.
*/
export declare function classifyInput(id: NodeId, dfg: DataflowGraph, config: InputClassifierConfig<InputClassifierFunctionIdentifiers>, fullDfg?: DataflowGraph, packages?: ReadonlySet<string>): InputSources;