Skip to content

Commit c77a5c5

Browse files
authored
Recognize s3s4 registrations (#2154)
* feat: rough up s4 * feat: rudimentary s3 method recognition
1 parent 9778ede commit c77a5c5

File tree

6 files changed

+87
-10
lines changed

6 files changed

+87
-10
lines changed

src/dataflow/environments/built-in.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type { DataflowInformation, ExitPoint, ExitPointType } from '../info';
33
import { processKnownFunctionCall } from '../internal/process/functions/call/known-call-handling';
44
import { processAccess } from '../internal/process/functions/call/built-in/built-in-access';
55
import { processIfThenElse } from '../internal/process/functions/call/built-in/built-in-if-then-else';
6-
import { processAssignment } from '../internal/process/functions/call/built-in/built-in-assignment';
6+
import {
7+
processAssignment,
8+
processAssignmentLike
9+
} from '../internal/process/functions/call/built-in/built-in-assignment';
710
import { processSpecialBinOp } from '../internal/process/functions/call/built-in/built-in-special-bin-op';
811
import { processPipe } from '../internal/process/functions/call/built-in/built-in-pipe';
912
import { processForLoop } from '../internal/process/functions/call/built-in/built-in-for-loop';
@@ -190,6 +193,7 @@ export const BuiltInProcessorMapper = {
190193
'builtin:access': processAccess,
191194
'builtin:apply': processApply,
192195
'builtin:assignment': processAssignment,
196+
'builtin:assignment-like': processAssignmentLike,
193197
'builtin:default': defaultBuiltInProcessor,
194198
'builtin:eval': processEvalCall,
195199
'builtin:expression-list': processExpressionList,

src/dataflow/environments/default-builtin-config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,8 @@ export const DefaultBuiltinConfig = [
251251
{ type: 'function', names: ['library', 'require'], processor: 'builtin:library', config: {}, assumePrimitive: false },
252252
{ type: 'function', names: ['<-', '='], processor: 'builtin:assignment', config: { canBeReplacement: true }, assumePrimitive: true },
253253
{ type: 'function', names: [':='], processor: 'builtin:assignment', config: {}, assumePrimitive: true },
254-
{ type: 'function', names: ['assign'], processor: 'builtin:assignment', config: { targetVariable: true }, assumePrimitive: true },
254+
{ type: 'function', names: ['assign', 'setGeneric', 'setValidity'], processor: 'builtin:assignment', config: { targetVariable: true }, assumePrimitive: true },
255+
{ type: 'function', names: ['setMethod'], processor: 'builtin:assignment-like', config: { targetVariable: true, canBeReplacement: false, target: { idx: 0, name: 'f' }, source: { idx: 2, name: 'definition' } }, assumePrimitive: true },
255256
{ type: 'function', names: ['delayedAssign'], processor: 'builtin:assignment', config: { quoteSource: true, targetVariable: true }, assumePrimitive: true },
256257
{ type: 'function', names: ['<<-'], processor: 'builtin:assignment', config: { superAssignment: true, canBeReplacement: true }, assumePrimitive: true },
257258
{ type: 'function', names: ['->'], processor: 'builtin:assignment', config: { swapSourceAndTarget: true, canBeReplacement: true }, assumePrimitive: true },
@@ -265,6 +266,7 @@ export const DefaultBuiltinConfig = [
265266
{ type: 'function', names: ['repeat'], processor: 'builtin:repeat-loop', config: {}, assumePrimitive: true },
266267
{ type: 'function', names: ['while'], processor: 'builtin:while-loop', config: {}, assumePrimitive: true },
267268
{ type: 'function', names: ['do.call'], processor: 'builtin:apply', config: { indexOfFunction: 0, unquoteFunction: true }, assumePrimitive: true },
269+
{ type: 'function', names: ['UseMethod', 'NextMethod'], processor: 'builtin:apply', config: { indexOfFunction: 0, unquoteFunction: true, resolveInEnvironment: 'global' }, assumePrimitive: true },
268270
{ type: 'function', names: ['.Primitive', '.Internal'], processor: 'builtin:apply', config: { indexOfFunction: 0, unquoteFunction: true, resolveInEnvironment: 'global' }, assumePrimitive: true },
269271
{ type: 'function', names: ['interference'], processor: 'builtin:apply', config: { unquoteFunction: true, nameOfFunctionArgument: 'propensity_integrand', libFn: true }, assumePrimitive: false },
270272
{ type: 'function', names: ['ddply'], processor: 'builtin:apply', config: { unquoteFunction: true, indexOfFunction: 2, nameOfFunctionArgument: '.fun', libFn: true }, assumePrimitive: false },

src/dataflow/internal/process/functions/call/built-in/built-in-assignment.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import type {
99
ParentInformation,
1010
RNodeWithParent
1111
} from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate';
12-
import type { RAstNodeBase, Location, RNode } from '../../../../../../r-bridge/lang-4.x/ast/model/model';
12+
import type { Location, RAstNodeBase, RNode } from '../../../../../../r-bridge/lang-4.x/ast/model/model';
1313
import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol';
1414
import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type';
15-
import type {
16-
EmptyArgument,
17-
RFunctionArgument
18-
} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
15+
import type { RFunctionArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
16+
import { EmptyArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
1917
import { type NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id';
2018
import { dataflowLogger } from '../../../../../logger';
2119
import {
@@ -73,6 +71,11 @@ export interface AssignmentConfiguration extends ForceArguments {
7371
readonly mayHaveMoreArgs?: boolean
7472
}
7573

74+
export interface ExtendedAssignmentConfiguration extends AssignmentConfiguration {
75+
readonly source: { idx?: number, name: string};
76+
readonly target: { idx?: number, name: string};
77+
}
78+
7679
function findRootAccess<OtherInfo>(node: RNode<OtherInfo & ParentInformation>): RSymbol<OtherInfo & ParentInformation> | undefined {
7780
let current = node;
7881
while(current.type === RType.Access) {
@@ -124,6 +127,42 @@ function tryReplacementPassingIndices<OtherInfo>(
124127
return info;
125128
}
126129

130+
/**
131+
* In contrast to `processAssignment`, this function allows more flexible handling of assignment-like functions.
132+
*/
133+
export function processAssignmentLike<OtherInfo>(
134+
name: RSymbol<OtherInfo & ParentInformation>,
135+
/* we expect them to be ordered in the sense that we have (source, target): `<source> <- <target>` */
136+
args: readonly RFunctionArgument<OtherInfo & ParentInformation>[],
137+
rootId: NodeId,
138+
data: DataflowProcessorInformation<OtherInfo & ParentInformation>,
139+
config: ExtendedAssignmentConfiguration
140+
): DataflowInformation {
141+
const argsWithNames = new Map<string, RFunctionArgument<OtherInfo & ParentInformation>>();
142+
const argsWithoutNames: RFunctionArgument<OtherInfo & ParentInformation>[] = [];
143+
for(const arg of args) {
144+
const name = arg !== EmptyArgument ? arg.name?.content : undefined;
145+
if(name !== undefined) {
146+
argsWithNames.set(name, arg);
147+
} else {
148+
argsWithoutNames.push(arg);
149+
}
150+
}
151+
const source = argsWithNames.get(config.source.name) ?? (config.source.idx !== undefined ? argsWithoutNames[config.source.idx] : undefined);
152+
const target = argsWithNames.get(config.target.name) ?? (config.target.idx !== undefined ? argsWithoutNames[config.target.idx] : undefined);
153+
if(source && target) {
154+
args = [target, source];
155+
}
156+
157+
return processAssignment<OtherInfo>(
158+
name,
159+
args,
160+
rootId,
161+
data,
162+
{ ...config, mayHaveMoreArgs: true }
163+
);
164+
}
165+
127166
/**
128167
* Processes an assignment, i.e., `<target> <- <source>`.
129168
* Handling it as a function call \`&lt;-\` `(<target>, <source>)`.

src/documentation/doc-readme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ The following showcases the dependency view of the [Visual Studio Code extension
203203
204204
![Dependency Analysis](https://raw.githubusercontent.com/flowr-analysis/vscode-flowr/refs/heads/main/media/dependencies.png)
205205
206-
`), ' ')}
206+
`), ' ')}
207207
208208
* 🚀 **fast call-graph, data-, and control-flow graphs**\\
209209
Within just [${'<i>' + textWithTooltip(roundToDecimals(await getLatestDfAnalysisTime('"social-science" Benchmark Suite (tree-sitter)'), 1) + ' ms', 'This measurement is automatically fetched from the latest benchmark!') + '</i>'} (as of ${new Date(await getLastBenchmarkUpdate()).toLocaleDateString('en-US', dateOptions)})](${FlowrSiteBaseRef}/wiki/stats/benchmark),

src/r-bridge/data/data.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ ${await printDfGraphForCode(parser, code, { simplified: true })}
725725
url: [
726726
{ name: AdvancedR('S3'), href: 'https://adv-r.hadley.nz/s3.html' }
727727
],
728-
supported: 'not',
728+
supported: 'partially',
729729
description: '_Handle S3 classes and methods as one unit (with attributes etc.). Including Dispatch and Inheritance._ We do not support typing currently and do not handle objects of these classes "as units."'
730730
},
731731
{
@@ -734,7 +734,7 @@ ${await printDfGraphForCode(parser, code, { simplified: true })}
734734
url: [
735735
{ name: AdvancedR('S4'), href: 'https://adv-r.hadley.nz/s4.html' }
736736
],
737-
supported: 'not',
737+
supported: 'partially',
738738
description: '_Handle S4 classes and methods as one unit. Including Dispatch and Inheritance_ We do not support typing currently and do not handle objects of these classes "as units."'
739739
},
740740
{

test/functionality/dataflow/processing-of-elements/atomic/dataflow-atomic.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,5 +910,37 @@ describe.sequential('Atomic (dataflow information)', withShell(shell => {
910910
.constant(1, undefined, false)
911911
.reads(6, 0)
912912
);
913+
914+
describe('S4 assign/get', () => {
915+
for(const fn of ['setGeneric', 'setValidity']) {
916+
assertDataflow(label(fn, ['oop-s4', 'strings', 'implicit-return', 'normal-definition', 'newlines', 'call-normal', 'numbers']),
917+
shell, `${fn}("a", function() 1)
918+
a()`, emptyGraph()
919+
.call(9, 'a', [], {
920+
returns: [3],
921+
reads: [1],
922+
environment: defaultEnv().defineFunction('a', 1, 7)
923+
})
924+
.calls(9, 5)
925+
.defineVariable(1, '"a"', { definedBy: [7, 5] })
926+
.call(7, fn, [argumentInCall(1), argumentInCall(5)], {
927+
returns: [1],
928+
onlyBuiltIn: true,
929+
reads: [builtInId(fn), 5],
930+
origin: ['builtin:assignment']
931+
})
932+
.calls(7, builtInId(fn))
933+
.defineFunction(5, [3], {
934+
entryPoint: 5,
935+
environment: defaultEnv().pushEnv(),
936+
graph: new Set([3]),
937+
in: [{ nodeId: 3, name: undefined, controlDependencies: [], type: ReferenceType.Constant }],
938+
out: [],
939+
unknownReferences: []
940+
})
941+
.constant(3, undefined, false)
942+
);
943+
}
944+
});
913945
});
914946
}));

0 commit comments

Comments
 (0)