@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
234 lines • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveByName = resolveByName;
exports.resolveByNameAnyType = resolveByNameAnyType;
exports.resolvesToBuiltInConstant = resolvesToBuiltInConstant;
const environment_1 = require("./environment");
const logic_1 = require("../../util/logic");
const identifier_1 = require("./identifier");
const info_1 = require("../info");
const built_in_s_seven_dispatch_1 = require("../internal/process/functions/call/built-in/built-in-s-seven-dispatch");
const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id");
/** A namespaced lookup only sees its matching layer; a bare lookup skips loaded-but-unattached namespaces (`requireNamespace`). */
function layerSkipped(layer, ns) {
if (ns !== undefined) {
return layer.n !== ns;
}
return layer.t === environment_1.EnvType.LoadedNamespace;
}
const FunctionTargetTypes = identifier_1.ReferenceType.Function | identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Parameter;
const VariableTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown;
const ConstantTargetTypes = identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown;
const BuiltInConstantTargetTypes = identifier_1.ReferenceType.BuiltInConstant | identifier_1.ReferenceType.Unknown;
const BuiltInFunctionTargetTypes = identifier_1.ReferenceType.BuiltInFunction | identifier_1.ReferenceType.Unknown;
const NonFunctionTargetTypes = identifier_1.ReferenceType.Variable | identifier_1.ReferenceType.Parameter | identifier_1.ReferenceType.Argument | identifier_1.ReferenceType.Unknown
| identifier_1.ReferenceType.Constant | identifier_1.ReferenceType.BuiltInConstant;
const inEveryBranch = (d) => (0, info_1.happensInEveryBranch)(d.cds);
const notAParameter = (d) => d.type !== identifier_1.ReferenceType.Parameter;
const TargetTypePredicate = {
[identifier_1.ReferenceType.Unknown]: () => true,
[identifier_1.ReferenceType.Function]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes),
[identifier_1.ReferenceType.Variable]: ({ type }) => (0, identifier_1.isReferenceType)(type, VariableTargetTypes),
[identifier_1.ReferenceType.Constant]: ({ type }) => (0, identifier_1.isReferenceType)(type, ConstantTargetTypes),
[identifier_1.ReferenceType.Parameter]: () => true,
[identifier_1.ReferenceType.Argument]: () => true,
[identifier_1.ReferenceType.BuiltInConstant]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInConstantTargetTypes),
[identifier_1.ReferenceType.BuiltInFunction]: ({ type }) => (0, identifier_1.isReferenceType)(type, BuiltInFunctionTargetTypes),
[identifier_1.ReferenceType.S3MethodPrefix]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes),
[identifier_1.ReferenceType.S7MethodPrefix]: ({ type }) => (0, identifier_1.isReferenceType)(type, FunctionTargetTypes),
[identifier_1.ReferenceType.NonFunction]: ({ type }) => (0, identifier_1.isReferenceType)(type, NonFunctionTargetTypes),
};
/**
* What the script itself defines under `id`, ignoring everything flowR carries or a database attached.
* This is the fallback of a value position ({@link ReferenceType.NonFunction}), and the two halves answer
* the two ways a value position meets a function of its name. In a project that attaches packages,
* `library(dplyr)` puts a function `id` in scope, and `filter(df, id > 2)` means the column, never that
* function. A script writing `x <- function() 1` and then `x > 2` has only that one `x` to mean, wrong as
* the comparison is, so the read stays: dropping it would leave the definition looking unused.
*/
function ownDefinitions(id, environment) {
const own = resolveByNameAnyType(id, environment)?.filter(d => !node_id_1.NodeId.isBuiltIn(d.nodeId));
return own !== undefined && own.length > 0 ? own : undefined;
}
/**
* Resolves a given identifier name to a list of its possible definition location using R scoping and resolving rules.
* If the type you want to reference is unknown, please use {@link resolveByNameAnyType} instead.
* @param id - The identifier to resolve (optionally namespaced)
* @param environment - The current environment used for name resolution
* @param target - The target (meta) type of the identifier to resolve
* @returns A list of possible identifier definitions (one if the definition location is exactly and always known), or `undefined`
* if the identifier is undefined in the current scope/with the current environment information.
* @useInstead {@link Resolve.byNameAndType}
*/
function resolveByName(id, environment, target) {
if (target === identifier_1.ReferenceType.Unknown) {
return resolveByNameAnyType(id, environment);
}
const found = resolveByTargetType(id, environment, target);
return found === undefined && target === identifier_1.ReferenceType.NonFunction ? ownDefinitions(id, environment) : found;
}
/** {@link resolveByName} without its two special targets, i.e. the plain walk up the environments. */
function resolveByTargetType(id, environment, target) {
/* read piecewise, `Identifier.toArray` would allocate a tuple per resolution */
const name = identifier_1.Identifier.getName(id);
const ns = identifier_1.Identifier.getNamespace(id);
const internal = identifier_1.Identifier.accessesInternal(id);
let current = environment.current;
/* `current` can already be the built-in environment itself (e.g. `get(x, envir=baseenv())`);
* it has no parent to walk to, so resolve directly instead of entering the loop below. */
if (current.builtInEnv) {
return current.memory.get(name);
}
let definitions = undefined;
const wantedType = TargetTypePredicate[target];
do {
if (layerSkipped(current, ns)) {
current = current.parent;
continue;
}
let definition;
if (target === identifier_1.ReferenceType.S3MethodPrefix || target === identifier_1.ReferenceType.S7MethodPrefix) {
// S3 method prefixes only resolve to functions, S3s must not match the exported criteria!
const prefix = name + (target === identifier_1.ReferenceType.S3MethodPrefix ? '.' : built_in_s_seven_dispatch_1.S7DispatchSeparator);
definition = current.memory.entries()
.filter(([defName]) => defName.startsWith(prefix))
.flatMap(([, defs]) => defs)
.toArray();
}
else {
definition = current.memory.get(name);
if (internal === false) {
definition = definition?.filter(({ name }) => name === undefined || !identifier_1.Identifier.accessesInternal(name));
}
}
if (definition !== undefined && definition.length > 0) {
/* ask before filtering: the common case wants all of them and needs no copy */
const allWanted = definition.every(wantedType);
if (allWanted && (target !== identifier_1.ReferenceType.Function || definition.every(notAParameter)) && definition.every(inEveryBranch)) {
return definition;
}
/* never alias the environment's own array, it is appended to below */
const filtered = allWanted ? definition.slice() : definition.filter(wantedType);
if (filtered.length > 0) {
if (definitions) {
definitions.push(...filtered);
}
else {
definitions = filtered;
}
}
}
current = current.parent;
} while (!current.builtInEnv);
/* the built-in layer is handed over as it is, except to a value position: `filter(df, c > 2)` means the
column `c`, never `base::c`, and only with nothing left does `ownDefinitions` have its say */
const known = current.memory.get(name);
const builtIns = target === identifier_1.ReferenceType.NonFunction ? known?.filter(wantedType) : known;
if (definitions) {
return builtIns === undefined || builtIns.length === 0 ? definitions : definitions.concat(builtIns);
}
return builtIns === undefined || builtIns.length > 0 ? builtIns : undefined;
}
/**
* The more performant version of {@link resolveByName} when the target type is unknown.
* @useInstead {@link Resolve.byName}
*/
function resolveByNameAnyType(id, environment) {
let current = environment.current;
/* only cache plain names: namespaced ids are arrays (no stable map key) and must not answer plain lookups */
const cacheable = typeof id === 'string';
if (cacheable) {
const g = current.cache?.get(id);
if (g !== undefined) {
return g;
}
}
const name = identifier_1.Identifier.getName(id);
const ns = identifier_1.Identifier.getNamespace(id);
const internal = identifier_1.Identifier.accessesInternal(id);
/* `current` can already be the built-in environment itself */
if (current.builtInEnv) {
const ret = current.memory.get(name);
if (ret && cacheable) {
current.cache ??= new Map();
current.cache.set(id, ret);
}
return ret;
}
let definitions = undefined;
do {
if (layerSkipped(current, ns)) {
current = current.parent;
continue;
}
let definition = current.memory.get(name);
if (definition) {
if (internal === false) {
definition = definition.filter(({ name }) => name === undefined || !identifier_1.Identifier.accessesInternal(name));
}
if (definition.every(inEveryBranch)) {
if (cacheable) {
environment.current.cache ??= new Map();
environment.current.cache.set(id, definition);
}
return definition;
}
else if (definition.length > 0) {
if (definitions) {
definitions = definitions.concat(definition);
}
else {
definitions = definition;
}
}
}
current = current.parent;
} while (!current.builtInEnv);
const builtIns = current.memory.get(name);
let ret;
if (definitions) {
ret = builtIns === undefined ? definitions : definitions.concat(builtIns);
}
else {
ret = builtIns;
}
if (ret && cacheable) {
environment.current.cache ??= new Map();
environment.current.cache.set(id, ret);
}
return ret;
}
/**
* Checks whether the given identifier name resolves to a built-in constant with the given value.
* @param name - The name of the identifier to resolve
* @param environment - The current environment used for name resolution
* @param wantedValue - The built-in constant value to check for
* @returns Whether the identifier always, never, or maybe resolves to the given built-in constant value
* @useInstead {@link Resolve.toBuiltIn}
*/
function resolvesToBuiltInConstant(name, environment, wantedValue) {
if (name === undefined) {
return logic_1.Ternary.Never;
}
const definition = resolveByName(name, environment, identifier_1.ReferenceType.Constant);
if (definition === undefined) {
return logic_1.Ternary.Never;
}
let all = true;
let some = false;
for (const def of definition) {
if (def.type === identifier_1.ReferenceType.BuiltInConstant && def.value === wantedValue) {
some = true;
}
else {
all = false;
}
}
if (all) {
return logic_1.Ternary.Always;
}
else {
return some ? logic_1.Ternary.Maybe : logic_1.Ternary.Never;
}
}
//# sourceMappingURL=resolve-by-name.js.map