Skip to content

Commit 7763d40

Browse files
author
Naomi Panda
committed
feat-fix: nested criteria
1 parent 4c6afdd commit 7763d40

2 files changed

Lines changed: 61 additions & 47 deletions

File tree

src/queries/catalog/call-context-query/call-context-query-executor.ts

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Identifier } from '../../../dataflow/environments/identifier';
2222
import { Dataflow } from '../../../dataflow/graph/df-helper';
2323
import { ArrayQueue } from '../../../util/collections/queue';
2424
import { baseRExportOwner } from '../../../util/r-base-packages';
25-
import { isNotUndefined } from '../../../util/assert';
25+
import { guard, isNotUndefined, isUndefined } from '../../../util/assert';
2626
import type { NodeId } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
2727
import { recoverContent, recoverName } from '../../../r-bridge/lang-4.x/ast/model/processing/node-id';
2828
import type { DataflowGraph } from '../../../dataflow/graph/graph';
@@ -38,12 +38,12 @@ import { SliceDirection } from '../../../util/slice-direction';
3838
import { RFunctionCall } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
3939
import { MatchArgs } from '../../../dataflow/graph/match-args';
4040
import { RFunctionDefinition } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-function-definition';
41-
import type { RSymbol } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
42-
import type { RArgument } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-argument';
4341
import { Resolve } from '../../../dataflow/environments/resolve-helper';
4442
import { VariableResolve } from '../../../config';
4543
import type { KnownParser } from '../../../r-bridge/parser';
4644
import { unwrapRValueToString, unliftRValue } from '../../../util/r-value';
45+
import { RBinaryOp } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-binary-op';
46+
import { RUnaryOp } from '../../../r-bridge/lang-4.x/ast/model/nodes/r-unary-op';
4747

4848
function makeReport(collector: TwoLayerCollector<string, string, CallContextQuerySubKindResult>): CallContextQueryKindResult {
4949
const result: CallContextQueryKindResult = {};
@@ -185,7 +185,7 @@ function retrieveAllCallAliases(nodeId: NodeId, graph: DataflowGraph): Map<strin
185185
}
186186
const [info, outgoing] = vertex;
187187

188-
if(FunctionCallVertex.is(info)) {
188+
if(!FunctionCallVertex.is(info)) {
189189
const wantedTypes = EdgeType.Reads | EdgeType.DefinedBy | EdgeType.DefinedByOnCall;
190190
const x = outgoing.entries()
191191
.filter(([,e]) => DfEdge.includesType(e, wantedTypes))
@@ -278,18 +278,18 @@ function isParameterDefaultValue(nodeId: NodeId, ast: NormalizedAst): boolean {
278278
return false;
279279
}
280280

281-
function resolveValueOfArgument(args: { name: string | undefined, id: NodeId }[], fCall: DataflowGraphVertexFunctionCall, value: string, graph: DataflowGraph<DataflowGraphVertexInfo, DfEdge>, analyzer: ReadonlyFlowrAnalysisProvider<KnownParser>): { dep: true, id: NodeId } | { dep: false }{
281+
function resolveValueOfArgument(args: { name: string | undefined, id: NodeId }[], fCall: DataflowGraphVertexFunctionCall, value: string, graph: DataflowGraph<DataflowGraphVertexInfo, DfEdge>, analyzer: ReadonlyFlowrAnalysisProvider<KnownParser>): { dep: true, call: undefined } | { dep: false }{
282282
for(const { id } of args){
283283
const resolved = Resolve.toValue(id, { environment: fCall.environment, graph, full: true, ctx: analyzer.inspectContext(), resolve: VariableResolve.Alias });
284284
const result = unwrapRValueToString(unliftRValue(resolved));
285285
if(result === value){
286-
return { dep: true, id: id };
286+
return { dep: true, call: undefined };
287287
}
288288
}
289289
return { dep: false };
290290
}
291291

292-
async function sliceAfterDep(args: { name: string | undefined, id: NodeId }[], dep: PromotedCallTest, graph: DataflowGraph<DataflowGraphVertexInfo, DfEdge>, analyzer: ReadonlyFlowrAnalysisProvider<KnownParser>, slicedParam: Set<NodeId>): Promise<{ dep: true, id: NodeId } | { dep: false }>{
292+
async function sliceAfterDep(args: { name: string | undefined, id: NodeId }[], dep: PromotedCallTest, graph: DataflowGraph<DataflowGraphVertexInfo, DfEdge>, analyzer: ReadonlyFlowrAnalysisProvider<KnownParser>, slicedParam: Set<NodeId>): Promise<{ dep: true, call: Required<DataflowGraphVertexFunctionCall> } | { dep: false }>{
293293
for(const { name, id } of args) {
294294
if(isNotUndefined(name)){
295295
if(slicedParam.has(name)){
@@ -306,32 +306,35 @@ async function sliceAfterDep(args: { name: string | undefined, id: NodeId }[], d
306306
for(const results of Object.values(argSlice['static-slice'].results)) {
307307
for(const resultId of results.slice.result) {
308308
const name = recoverName(resultId, graph.idMap);
309-
if(name && dep(name) && FunctionCallVertex.is(graph.getVertex(resultId))) {
310-
return { dep: true, id: id };
309+
const vertex = graph.getVertex(resultId);
310+
if(name && dep(name) && FunctionCallVertex.is(vertex)) {
311+
return { dep: true, call: vertex };
311312
}
312313
}
313314
}
314315
}
315316
return { dep: false };
316317
}
317318

318-
async function isDependentOn(parameter: string, dep: PromotedCallTest | undefined, value: string | undefined, fCall: Required<DataflowGraphVertexFunctionCall>, graph: DataflowGraph, analyzer: ReadonlyFlowrAnalysisProvider): Promise<{ dep: true, id: NodeId } | { dep: false }> {
319+
async function isDependentOn(parameter: string, dep: PromotedCallTest | undefined, value: string | undefined, fCall: Required<DataflowGraphVertexFunctionCall>, graph: DataflowGraph, analyzer: ReadonlyFlowrAnalysisProvider): Promise<{ dep: true, call: Required<DataflowGraphVertexFunctionCall> | undefined } | { dep: false }> {
319320
const astCall = graph.idMap?.get(fCall?.id) as RFunctionCall<ParentInformation> | undefined;
320-
if(!RFunctionCall.is(astCall)) {
321+
if(!RFunctionCall.is(astCall) && !RBinaryOp.is(astCall) && !RUnaryOp.is(astCall)) {
321322
return { dep: false };
322323
}
323324
const defs = MatchArgs.toDefinition(astCall, graph, analyzer.inspectContext());
324325
// TODO: match against signaue (so that we can for example slice for the x of a print)
325-
const slicedParam = new Set<NodeId>();
326+
const slicedParam = new Set<string>();
326327
const isParam = parameter === '*' ? () => true : (p: string | undefined) => p === parameter;
328+
//todo: den fall nachher raus?
327329
if(defs === undefined){
328330
if(isNotUndefined(dep)){
329331
//e.g. f(getOption(x)), searching for dep 'getOption'
330332
for(const arg of fCall.args){
331333
if(!FunctionArgument.isEmpty(arg)){
332-
const rArg = graph.idMap?.get(arg.nodeId) as RArgument;
333-
if(rArg.value?.type === 'RFunctionCall' && isNotUndefined((rArg.value?.functionName as RSymbol).content) && dep(Identifier.getName((rArg.value?.functionName as RSymbol).content))){
334-
return { dep: true, id: arg.nodeId };
334+
const name = recoverName(arg.nodeId, graph.idMap);
335+
const vertex = graph.getVertex(arg.nodeId);
336+
if(name && dep(name) && FunctionCallVertex.is(vertex)){
337+
return { dep: true, call: vertex };
335338
}
336339
}
337340
}
@@ -362,8 +365,12 @@ async function isDependentOn(parameter: string, dep: PromotedCallTest | undefine
362365
if(isNotUndefined(dep)){
363366
//e.g. f(getOption(x)), searching for dep 'getOption'
364367
for(const [_, arg] of possibleArgs){
365-
if(arg.value?.type === 'RFunctionCall' && isNotUndefined((arg.value?.functionName as RSymbol).content) && dep(Identifier.getName((arg.value?.functionName as RSymbol).content))){
366-
return { dep: true, id: arg.info.id };
368+
if(isNotUndefined(arg.value?.info.id)){
369+
const name = recoverName(arg.value?.info.id, graph.idMap);
370+
const vertex = graph.getVertex(arg.value.info.id);
371+
if(name && dep(name) && FunctionCallVertex.is(vertex)){
372+
return { dep: true, call: vertex };
373+
}
367374
}
368375
}
369376
}
@@ -391,14 +398,14 @@ async function isDependentOn(parameter: string, dep: PromotedCallTest | undefine
391398
if(isNotUndefined(defId)){
392399
const def = graph.idMap?.get(defId);
393400
if(RFunctionDefinition.is(def)){
394-
const params = def.parameters.filter(param => {
395-
if(isParam(param.name.lexeme) && !slicedParam.has(param.name.lexeme) && isNotUndefined(param.defaultValue?.info.id)){
401+
const params = def.parameters.map(param => {
402+
return { name: recoverName(param.info.id, graph.idMap), id: param.defaultValue?.info.id };
403+
}).filter(({ name, id }) => {
404+
if(isParam(name) && isNotUndefined(name) && !slicedParam.has(name) && isNotUndefined(id)){
396405
return true;
397406
}
398407
return false;
399-
}).map(param => {
400-
return { name: param.name.lexeme, id: param.defaultValue?.info.id as NodeId };
401-
});
408+
}) as { name: string | undefined; id: NodeId }[];
402409
if(isNotUndefined(dep)){
403410
const sliced = await sliceAfterDep(params, dep, graph, analyzer, slicedParam);
404411
if(sliced.dep){
@@ -526,7 +533,8 @@ export async function executeCallContextQueries({ analyzer }: BasicQueryData, qu
526533
if(query.reliesOnCriteria) {
527534
let isDependent = true;
528535
let fCall = info;
529-
for(const entry of query.reliesOnCriteria) {
536+
for(let i = 0; i < query.reliesOnCriteria.length; i++) {
537+
const entry = query.reliesOnCriteria[i];
530538
const name = entry.name;
531539
const calls = Object.hasOwn(entry, 'calls') ? (entry as ParameterConstraintWithCall).calls : undefined;
532540
const value = Object.hasOwn(entry, 'value') ? (entry as ParameterConstraintWithValue).value : undefined;
@@ -535,9 +543,15 @@ export async function executeCallContextQueries({ analyzer }: BasicQueryData, qu
535543
isDependent = false;
536544
break;
537545
} else {
538-
const vertex = dataflowGraph.getVertex(res.id);
539-
if(FunctionCallVertex.is(vertex)){
540-
fCall = vertex;
546+
//case: we try to further resolve a value
547+
if(isUndefined(res.call)){
548+
if(i !== query.reliesOnCriteria.length - 1){
549+
guard(true, 'A value cannot be resolved further, wrong criteria');
550+
isDependent = false;
551+
}
552+
break;
553+
} else {
554+
fCall = res.call;
541555
}
542556
}
543557
}

test/functionality/dataflow/query/call-context-query.test.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -164,31 +164,30 @@ describe('Call Context Query', withTreeSitter(parser => {
164164
testQuery('Print calls', 'print(1)', [q('pr')], r([{ id: 3, name: 'print' }]));
165165
testQuery('With compaction optimization', 'print(1)', new Array(10000).fill(q('print')), r([{ id: 3, name: 'print' }]));
166166
});
167-
describe('Ask for args', () => {
168-
testQuery('1', 'print(getOption("x", default = 1)); print(4)',
169-
[q(/print/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }] })], r([{ id: 9, name: 'print' }]),
167+
describe('Arguments rely on criteria', () => {
168+
testQuery('Simple direct call', 'print(getOption("x", default = 1)); print(4)',
169+
[q(/print/, { reliesOnCriteria: [{ name: 'x', calls: 'getOption' }] })], r([{ id: 9, name: 'print' }])
170170
);
171-
testQuery('2', 'b <- getOption("x", default = 1); a <- 4 + b; print(a); print(4)', [q(/print/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }] })], r([{ id: 17, name: 'print' }]));
172-
testQuery('3', 'a <- 4 + 3; b <- 3; c <- 4+b; a <- 8; print(a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: '*', calls: '\\+' }] })], r([{ id: 27, name: 'print' }]));
173-
testQuery('4', 'a <- 4 + 3*2; b <- 3; c <- 4+b; print(x = a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: 'x', calls: '\\+' }] })], r([{ id: 19, name: 'print' }]));
174-
175-
testQuery('5', 'a <- 4 + 3*2; b <- 3; c <- 4+b; print(x = a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: 'x', calls: '\\+' }, { name: '*', calls: '\\*' }] })], r([{ id: 19, name: 'print' }]));
176-
testQuery('6', `f <- function(x = foo()) {
171+
testQuery('Simple 2', 'b <- getOption("x", default = 1); a <- 4 + b; print(a); print(4)', [q(/print/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }] })], r([{ id: 17, name: 'print' }]));
172+
testQuery('Simple 3', 'a <- 4 + 3; b <- 3; c <- 4+b; a <- 8; print(a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: '*', calls: '\\+' }] })], r([{ id: 27, name: 'print' }]));
173+
testQuery('Simple 4', 'a <- 4 + 3*2; b <- 3; c <- 4+b; print(x = a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: 'x', calls: '\\+' }] })], r([{ id: 19, name: 'print' }]));
174+
testQuery('Simple nested', 'a <- 4 + 3*2; b <- 3; c <- 4+b; print(x = a); print(b); print(c)', [q(/print/, { reliesOnCriteria: [{ name: '*', calls: '\\+' }, { name: '*', calls: '\\*' }] })], r([{ id: 19, name: 'print' }]));
175+
testQuery('Relies on with default value', `f <- function(x = foo()) {
177176
print(x)
178177
}
179178
foo <- function() getOption("bar")
180179
f()
181180
f(x=42)
182181
f(42)
183-
f(getOption("bar"))`, [q(/^f$/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }] })], r([{ id: 23, name: 'f' }, { id: 39, name: 'f' }]));
182+
f(getOption("bar"))`, [q(/^f$/, { reliesOnCriteria: [{ name: 'x', calls: 'getOption' }] })], r([{ id: 23, name: 'f' }, { id: 39, name: 'f' }]));
184183

185-
testQuery('7', `f <- function(x = foo()) {
184+
testQuery('Direct call - own function', `f <- function(x = foo()) {
186185
print(x)
187186
}
188187
foo <- function() getOption("bar")
189188
f(x=getOption("bar"))`, [q(/^f$/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }] })], r([{ id: 29, name: 'f' }]));
190189

191-
testQuery('values', `f <- function(x = foo()) {
190+
testQuery('Relies on value', `f <- function(x = foo()) {
192191
print(x)
193192
}
194193
foo <- function() getOption("bar")
@@ -197,21 +196,22 @@ b <- a
197196
f(b)`, [q(/^f$/, { reliesOnCriteria: [{ name: '*', value: '2' }] })], r([{ id: 31, name: 'f' }]));
198197

199198

200-
testQuery('8', `f <- function(x = foo()) {
199+
testQuery('Relies on nested criteria', `f <- function(x = foo()) {
201200
print(x)
202201
}
203202
foo <- function() getOption("bar")
204203
f(getOption(toString(4)))
205-
f()`, [q(/f/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }, { name: '*', calls: 'toString' }] })], r([{ id: 31, name: 'f' }]));
204+
f(getOption("bar"))
205+
f(getOption(toString(3)))`, [q(/f/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }, { name: '*', calls: 'toString' }, { name: '*', value: '4' }] })], r([{ id: 31, name: 'f' }]));
206206

207-
});
208-
//todo: remove after testing
209-
describe('Remove after testing', () => {
210-
testQuery('8', `f <- function(x = foo()) {
207+
testQuery('Relies on nested criteria - default values', `f <- function(x = foo()) {
211208
print(x)
212209
}
213-
foo <- function() getOption("bar")
214-
f(getOption(toString(4)))
215-
f()`, [q(/f/, { reliesOnCriteria: [{ name: '*', calls: 'getOption' }, { name: '*', calls: 'toString' }] })], r([{ id: 31, name: 'f' }]));
210+
foo <- function(x = 5) print(y)
211+
f()
212+
f(4)`, [q(/^f$/, { reliesOnCriteria: [{ name: 'x', calls: 'foo' }, { name: 'x', value: '5' }] })], r([{ id: 26, name: 'f' }]));
213+
214+
216215
});
217216
}));
217+

0 commit comments

Comments
 (0)