From dd47946bc544ebfc4318a14e3744f89c96c0a96b Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 29 May 2026 22:16:10 +0200 Subject: [PATCH 1/2] feat: resolve built in constants in graph without environment --- src/dataflow/eval/resolve/alias-tracking.ts | 7 +++- src/dataflow/graph/vertex.ts | 18 ++++----- .../call/built-in/built-in-expression-list.ts | 10 +++++ .../dataflow/environments/resolve.test.ts | 37 ++++++++++++++----- .../main/atomic/dataflow-atomic.test.ts | 15 ++++++++ 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/dataflow/eval/resolve/alias-tracking.ts b/src/dataflow/eval/resolve/alias-tracking.ts index 35f3ef91e1e..ed6b84f3323 100644 --- a/src/dataflow/eval/resolve/alias-tracking.ts +++ b/src/dataflow/eval/resolve/alias-tracking.ts @@ -14,7 +14,7 @@ import { DfEdge, EdgeType } from '../../graph/edge'; import type { DataflowGraph } from '../../graph/graph'; import { onReplacementOperator, type ReplacementOperatorHandlerArgs } from '../../graph/unknown-replacement'; import { onUnknownSideEffect } from '../../graph/unknown-side-effect'; -import { VertexType } from '../../graph/vertex'; +import { isValueVertex, VertexType } from '../../graph/vertex'; import { valueFromRNodeConstant, valueFromTsValue } from '../values/general'; import { Bottom, isTop, type Lift, Top, type Value, type ValueSet } from '../values/r-value'; import { setFrom } from '../values/sets/set-constants'; @@ -380,6 +380,11 @@ export function trackAliasesInGraph(id: NodeId, graph: DataflowGraph, ctx: ReadO const values: Set = new Set(); for(const id of resultIds) { + const vertex = graph.getVertex(id); + if(isValueVertex(vertex) && vertex.value !== undefined) { + values.add(vertex.value); + continue; + } const node = idMap.get(id); if(node !== undefined) { if(node.info.role === RoleInParent.ParameterDefaultValue || RNode.iterateParents(node, idMap).some(p => p.info.role === RoleInParent.ParameterDefaultValue)) { diff --git a/src/dataflow/graph/vertex.ts b/src/dataflow/graph/vertex.ts index f9a5f256196..ebc07e8d9d5 100644 --- a/src/dataflow/graph/vertex.ts +++ b/src/dataflow/graph/vertex.ts @@ -5,6 +5,7 @@ import type { REnvironmentInformation } from '../environments/environment'; import type { ControlDependency, ExitPoint } from '../info'; import type { Identifier } from '../environments/identifier'; import type { BuiltInProcName } from '../environments/built-in-proc-name'; +import type { Value } from '../eval/values/r-value'; export enum VertexType { @@ -56,27 +57,22 @@ export interface DataflowGraphVertexAstLink { /** * Marker vertex for a value in the dataflow of the program. - * This does not contain the _value_ of the referenced constant - * as this is available with the {@link DataflowGraphVertexBase#id|id} in the {@link NormalizedAst|normalized AST} - * (or more specifically the {@link AstIdMap}). - * - * If you have a {@link DataflowGraph|dataflow graph} named `graph` - * with an {@link AstIdMap} and a value vertex object with name `value` the following Code should work: + * For user-code constants (numbers, strings, logicals) the value is recovered by looking up the + * {@link DataflowGraphVertexBase#id|id} in the {@link NormalizedAst|normalized AST}: * @example * ```ts * const node = graph.idMap.get(value.id) * ``` * - * This then returns the corresponding node in the {@link NormalizedAst|normalized AST}, for example, - * an {@link RNumber} or {@link RString}. - * - * This works similarly for {@link IdentifierReference|identifier references} - * for which you can use the {@link IdentifierReference#nodeId|`nodeId`}. + * For built-in constants whose id is not in the {@link AstIdMap} (e.g. `T` resolving to `built-in:T`), + * the abstract {@link Value} is stored directly in the {@link DataflowGraphVertexValue#value|value} field. * @see {@link isValueVertex} - to check if a vertex is a value vertex */ export interface DataflowGraphVertexValue extends DataflowGraphVertexBase { readonly tag: VertexType.Value readonly environment?: undefined + /** Pre-computed abstract value; set for built-in constants (e.g. `T`, `F`) whose id is not in the AST id map */ + readonly value?: Value } /** diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts index 1fd05732ab5..207434f2d13 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts @@ -26,6 +26,8 @@ import { expensiveTrace } from '../../../../../../util/log'; import type { Writable } from 'ts-essentials'; import { makeAllMaybe } from '../../../../../environments/reference-to-maybe'; import { BuiltInProcName } from '../../../../../environments/built-in-proc-name'; +import type { BuiltInIdentifierConstant } from '../../../../../environments/built-in'; +import { valueFromTsValue } from '../../../../../eval/values/general'; @@ -60,6 +62,14 @@ function linkReadNameToWriteIfPossible(read: IdentifierReference, environments: } else { nextGraph.addEdge(rid, tid, EdgeType.Reads); } + if(target.type === ReferenceType.BuiltInConstant) { + nextGraph.addVertex({ + tag: VertexType.Value, + id: tid, + cds: undefined, + value: valueFromTsValue((target as BuiltInIdentifierConstant).value) + }, environments); + } } } diff --git a/test/functionality/dataflow/environments/resolve.test.ts b/test/functionality/dataflow/environments/resolve.test.ts index 4c5d9f428ea..a2736634f97 100644 --- a/test/functionality/dataflow/environments/resolve.test.ts +++ b/test/functionality/dataflow/environments/resolve.test.ts @@ -19,14 +19,21 @@ import { resolveIdToValue, resolveToConstants } from '../../../../src/dataflow/e import { contextFromInput } from '../../../../src/project/context/flowr-analyzer-context'; import { FlowrConfig } from '../../../../src/config'; +/** Controls which extra results are accepted in addition to an exact match */ enum Allow { - None = 0, - Top = 1, - Bottom = 2 + /** Only the exact expected value is accepted */ + ExactOnly = 0, + /** Also accept top (unknown); use when the analysis may give up */ + Top = 1, + /** Also accept bottom (unreachable); use when the result may be unreachable */ + Bottom = 2 } +/** Controls how {@link resolveIdToValue} is invoked during tests */ enum With { - Graph, + /** Pass only the dataflow graph, no environment is given to the resolver */ + GraphOnly, + /** Pass the full program environment, the normal fully-informed path */ Environment } @@ -49,8 +56,8 @@ describe.sequential('Resolve', withShell(shell => { } function testWithGraphAndEnvironment(name: string, tests: (withWhat: With) => void) { - describe(`${name} (Graph)`, () => { - tests(With.Graph); + describe(`${name} (Graph only)`, () => { + tests(With.GraphOnly); }); describe(`${name} (Environment)`, () => { @@ -63,7 +70,7 @@ describe.sequential('Resolve', withShell(shell => { identifier: SlicingCriterion, code: string, expectedValues: Value, - allow: Allow = Allow.None, + allow: Allow = Allow.ExactOnly, withEnv: With = With.Environment ): void { const effectiveName = decorateLabelContext(label(name), ['resolve']); @@ -76,7 +83,7 @@ describe.sequential('Resolve', withShell(shell => { }).allRemainingSteps(); const resolved = resolveIdToValue(SlicingCriterion.parse(identifier, dataflow.normalize.idMap), { - environment: withEnv === With.Environment ? dataflow.dataflow.environment : undefined, + environment: withEnv === With.GraphOnly ? undefined : dataflow.dataflow.environment, graph: dataflow.dataflow.graph, idMap: dataflow.normalize.idMap, full: true, @@ -96,7 +103,7 @@ describe.sequential('Resolve', withShell(shell => { }); } - function testMutate(name: string, line: number, identifier: string, code: string, expected: Value, allow: Allow = Allow.None) { + function testMutate(name: string, line: number, identifier: string, code: string, expected: Value, allow: Allow = Allow.ExactOnly) { const distractors: string[] = [ `while(FALSE) { ${identifier} <- 0 }`, `if(FALSE) { ${identifier} <- 0 }`, @@ -210,6 +217,18 @@ describe.sequential('Resolve', withShell(shell => { }); }); }); + describe.each<[string, With]>([ + ['Graph only', With.GraphOnly ], + ['Environment', With.Environment], + ])('Resolve built-in constants (%s)', (_name, resolveWith) => { + testResolve('T resolves to true', '1@T', 'T', set([true]), Allow.ExactOnly, resolveWith); + testResolve('F resolves to false', '1@F', 'F', set([false]), Allow.ExactOnly, resolveWith); + testResolve('TRUE resolves to true', '1@TRUE', 'TRUE', set([true]), Allow.ExactOnly, resolveWith); + testResolve('T multiple, resolve second', '2@T', 'T\nT\nT', set([true]), Allow.ExactOnly, resolveWith); + testResolve('T shadow with logical', '2@T', 'T <- FALSE\nT', set([false]), Allow.ExactOnly, resolveWith); + testResolve('T shadow with number', '2@T', 'T <- 42\nT', set([42]), Allow.ExactOnly, resolveWith); + }); + describe('Builtin Constants', () => { // Always Resolve test.each([ diff --git a/test/functionality/dataflow/main/atomic/dataflow-atomic.test.ts b/test/functionality/dataflow/main/atomic/dataflow-atomic.test.ts index 63d645ce11f..be2aec46df0 100644 --- a/test/functionality/dataflow/main/atomic/dataflow-atomic.test.ts +++ b/test/functionality/dataflow/main/atomic/dataflow-atomic.test.ts @@ -41,6 +41,21 @@ describe.sequential('Atomic (dataflow information)', withShell(shell => { } }); + describe('Built-in constant aliases', () => { + for(const [input, capability] of [ + ['T', 'logical'], + ['F', 'logical'], + ] as [string, SupportedFlowrCapabilityId][]) { + assertDataflow(label(input, [capability]), shell, input, + emptyGraph() + .use(0, input) + .constant(NodeId.toBuiltIn(input), {}, false) + .reads(0, NodeId.toBuiltIn(input)), + { expectIsSubgraph: true } + ); + } + }); + assertDataflow(label('simple variable', ['name-normal']), shell, 'xylophone', emptyGraph().use(0, 'xylophone') ); From 751f2b5d375dbfa07642bf45837273629f7519f0 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 29 May 2026 22:24:16 +0200 Subject: [PATCH 2/2] refactor: clean up root vertices --- .../process/functions/call/built-in/built-in-expression-list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts index 207434f2d13..6f26ee74494 100644 --- a/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-expression-list.ts @@ -68,7 +68,7 @@ function linkReadNameToWriteIfPossible(read: IdentifierReference, environments: id: tid, cds: undefined, value: valueFromTsValue((target as BuiltInIdentifierConstant).value) - }, environments); + }, environments, false); } } }