Skip to content

fix: show FEEL expression for unresolvable function invocations#104

Draft
barinali wants to merge 2 commits intomainfrom
issue-5744-show-expression-for-unresolvable-values
Draft

fix: show FEEL expression for unresolvable function invocations#104
barinali wants to merge 2 commits intomainfrom
issue-5744-show-expression-for-unresolvable-values

Conversation

@barinali
Copy link
Copy Markdown
Member

@barinali barinali commented Apr 18, 2026

Related to camunda/camunda-modeler#5744

Proposed Changes

This pull request aims to preserve the FEEL expression for the built-in functions for downstream to display them on the user-interface. This pull request is enabled by lezer-feel changes.

Before

image

After

image

Tests

The tests can be observed in #105 with the necessary changes from the lezer-feel pull request.

Steps to try out

This is the command I'd have suggested to experiment with variable over variable-outline, but I had no luck having it run so far. I think it's failing because linked dependencies are not direct dependencies of variable-outline and they're not declared as overrides by @bpmn-io/sr.

npx @bpmn-io/sr bpmn-io/variable-outline -l bpmn-io/variable-resolver#issue-5744-show-expression-for-unresolvable-values -l bpmn-io/lezer-feel#issue-5744-fix-function-invocation-value

Checklist

Ensure you provide everything we need to review your contribution:

  • Contribution meets our definition of done
  • Pull request establishes context
    • Link to related issue(s), i.e. Closes {LINK_TO_ISSUE} or Related to {LINK_TO_ISSUE}
    • Brief textual description of the changes
    • Screenshots or short videos showing UI/UX changes
    • Steps to try out, i.e. using the @bpmn-io/sr tool

@barinali barinali self-assigned this Apr 18, 2026
@barinali barinali requested review from a team and Copilot April 18, 2026 00:14
@barinali barinali added the bug Something isn't working label Apr 18, 2026
@bpmn-io-tasks bpmn-io-tasks Bot added the needs review Review pending label Apr 18, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts Zeebe FEEL expression resolution to preserve the original FEEL expression when function invocations cannot be resolved at design time, enabling downstream UI to display the expression instead of an empty value.

Changes:

  • Preserve FEEL expression as info for unresolved (type Any) function invocation results.
  • Normalize certain unresolved leaf entries in computed context results for display purposes.
  • Add regression tests + a BPMN fixture covering function invocation cases; update changelog.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
test/spec/zeebe/ZeebeVariableResolver.spec.js Adds tests ensuring unresolved function calls expose the FEEL expression and nested context results are retained.
test/fixtures/zeebe/function-invocation.bpmn New BPMN fixture with output mappings using FEEL built-in function invocations and a context-defined function.
lib/zeebe/util/feelUtility.js Updates FEEL resolution to preserve expressions for unresolved results; adjusts unified-format conversion for unresolved leaf entries.
CHANGELOG.md Documents the fix in the Unreleased section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/zeebe/util/feelUtility.js Outdated
Comment on lines +670 to +675
// Entries with unknown type, no info, and no sub-entries are values
// that cannot be determined at design time (e.g. nested results of
// unresolvable function invocations). In FEEL, absent values are null,
// there is no "unknown" concept. So we normalize these to Null.
if (annotated.type === 'Any' && !annotated.info &&
(!entries || !entries.length)) {
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new AnyNull normalization will rewrite any leaf entry with unknown type + no info into { type: 'Null', info: 'null' }. That can misrepresent legitimate “unknown at design time” values (e.g. context outputs that forward runtime-provided variables) as proven-null. Consider narrowing this condition (e.g. only normalize when atomicValue === null, or add a guard to avoid rewriting values that originate from scoped variables).

Suggested change
// Entries with unknown type, no info, and no sub-entries are values
// that cannot be determined at design time (e.g. nested results of
// unresolvable function invocations). In FEEL, absent values are null,
// there is no "unknown" concept. So we normalize these to Null.
if (annotated.type === 'Any' && !annotated.info &&
(!entries || !entries.length)) {
// Only normalize unknown leaf values to FEEL null when the source value
// explicitly represents null. Other leaf `Any` values may simply be
// unknown at design time (for example, forwarded scoped/runtime values)
// and must not be rewritten as proven-null.
if (annotated.type === 'Any' && !annotated.info &&
(!entries || !entries.length) &&
has(variable, 'atomicValue') && variable.atomicValue === null) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should simply extract this to a helper function - completely unreadable code - much more readable like this:

if (isNullishValue(...)) {
  ...
}

Comment thread lib/zeebe/util/feelUtility.js Outdated
@barinali barinali force-pushed the issue-5744-show-expression-for-unresolvable-values branch from 3095f41 to a867dcc Compare April 18, 2026 00:30
@barinali barinali requested review from Buckwich and nikku April 18, 2026 00:31
Copy link
Copy Markdown
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the potential to type functions - they all offer more or less a contract. I.e. abs will always return number|null.

Comment thread lib/zeebe/util/feelUtility.js Outdated
// Entries with unknown type, no info, and no sub-entries are values
// that cannot be determined at design time (e.g. nested results of
// unresolvable function invocations). In FEEL, absent values are null,
// there is no "unknown" concept. So we normalize these to Null.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we'd normalize these to Any (unknown).

Copy link
Copy Markdown
Member Author

@barinali barinali Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Any, we use undefined as the value, but undefined does not exist in both JSON and FEEL specifications. So, while stringifying JSON, we lose undefined properties.

Not to lose them, I re-value them as null as it'll resolve as null from engine perspective to my understanding. By doing so, we keep the property in place instead of losing it.

updated thoughts below;

While info itself is important here more than the type for the value we display in variable-outline, marking this Any does not quite feel right as well.

In abs(...) scenario you mentioned previously, it's number|null, not Any clearly. Marking it as null looks closer to the possible types while both null or Any alone is somewhat misleading.

We do not show the type on variable-outline still, but properties-panel do show the variable type while giving suggestions. There, would you say it's better to see Any or Null, @nikku?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not show the type on variable-outline still, but properties-panel do show the variable type while giving suggestions. There, would you say it's better to see Any or Null, @nikku?

As I previously indicated I'd not show values where we don't know where they come from ("unknown") as null. While mapped as null by the engine, i.e. for empty input mappings, the semantics are typically "this is a local variable, someone else defines the value" - I'd love to see that clearly represented.

Copy link
Copy Markdown
Member Author

@barinali barinali Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I previously indicated I'd not show values where we don't know where they come from ("unknown") as null.

Sure, we are on the same page. For the variables, having a value as a direct function call is covered in this way.

# Variable name Variable assignment value Type Context
1 casting_number =number("123") Any { }
2 deepFuncCall ={ a: function() { aa: { bb: abs(y) } }, b: a()}.b Any { }
Screenshots for variable in the row 1.
image image
Screenshots for variable in the row 2.
image

I think, until here, it's all good. But when we are to display a context value (as JSON in CodeMirror), undefined properties are naturally being omitted while building CodeMirror document (via JSON.stringify).

At this point, I have thought we need to normalize these to null so that properties are not lost as we cannot currently compute any value for them (neither actual computed value or the FEEL expressions to display for that matter).

If I do not normalize it as null (regardless of its type, I can leave that as Any as in the screenshot if that makes sense), this is what we display:

image

Compared to

image

I'd be happy to hear suggestions to improve the view. I hope I could bring clarity for the motivation behind the implementation details.

@barinali barinali force-pushed the issue-5744-show-expression-for-unresolvable-values branch from a867dcc to 1839f6d Compare April 20, 2026 11:22
@nikku nikku marked this pull request as draft April 20, 2026 11:27
@bpmn-io-tasks bpmn-io-tasks Bot added in progress Currently worked on and removed needs review Review pending labels Apr 20, 2026
@nikku
Copy link
Copy Markdown
Member

nikku commented Apr 20, 2026

@barinali turned to draft. Let's get this back into review once upstream is incorporated.

@barinali barinali marked this pull request as ready for review April 20, 2026 13:40
@bpmn-io-tasks bpmn-io-tasks Bot added needs review Review pending and removed in progress Currently worked on labels Apr 20, 2026
@barinali barinali marked this pull request as draft April 22, 2026 16:17
@bpmn-io-tasks bpmn-io-tasks Bot added in progress Currently worked on and removed needs review Review pending labels Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working in progress Currently worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants