Skip to content

CDKTN: ForExpression missing iteratorContext and OperatorExpression drops falsy right operands #111

@X-Guardian

Description

@X-Guardian

Expected Behavior

  1. TerraformIterator.fromComplexList(list, "key").forExpressionForList(expr) should iterate the raw list directly (as it does when used via TerraformDynamicExpression), producing [ for key, val in <list>: <expr> ].

  2. Op.gt(someExpr, 0) (and other operators with falsy right-hand operands like 0, false, "") should render correctly, e.g. ${(length(...) > 0)}.

Actual Behavior

  1. forExpressionForList on a fromComplexList iterator always produces the map conversion { for key, val in <list>: val.<keyAttr> => val } instead of using the raw list. This causes a Duplicate object key error when multiple items share the same key attribute value.

  2. Op.gt(someExpr, 0) renders as ${(length(...) > undefined)} because 0 is falsy, causing a Terraform Unknown variable "undefined" error.

Steps to Reproduce

Bug 1: ForExpression missing iteratorContext

  1. Create a data source that returns a list of objects where the key attribute is not unique:
    const data = new DataSource(this, "data", {});
    const it = TerraformIterator.fromComplexList(data.items, "name");
  2. Use forExpressionForList to filter the list:
    new Resource(this, "res", {
      value: Fn.element(it.forExpressionForList(`val.id if val.name == "foo"`), 0),
    });
  3. Run cdktn synth and observe the generated JSON uses the map conversion:
    element([ for key, val in { for key, val in data.source.data.items: val.name => val }: val.id if key == "foo"], 0)
    
  4. Run terraform plan and observe Duplicate object key error.

Bug 2: OperatorExpression drops falsy operands

  1. Use any operator with 0, false, or "" as the right operand:
    Op.gt(Fn.lengthOf(someList), 0)
  2. Run cdktn synth and observe the output contains > undefined instead of > 0.

Versions

language: typescript
cdktn-cli: 0.22.0
node: v22.16.0
cdktn: 0.22.0
constructs: 10.3.0
jsii: 5.8.9
terraform: 1.5.5
arch: arm64
os: darwin 26.3.1

Migration Context

I used cdktf and saw the same issue there

Upstream cdktf Issue

No response

Providers

N/A

Gist

No response

Possible Solutions

Both fixes are in packages/cdktn/lib/tfExpression.ts:

Bug 1: ForExpression.resolve() does not set iteratorContext

DynamicListTerraformIterator._getForEachExpression() returns a Lazy that checks context.iteratorContext — returning the raw list for "FOR_EXPRESSION" and the map conversion for the default case. TerraformDynamicExpression.resolve() correctly sets context.iteratorContext = "FOR_EXPRESSION" before resolving, but ForExpression.resolve() does not.

Fix: Add context.iteratorContext = "FOR_EXPRESSION" in ForExpression.resolve():

  public resolve(context: IResolveContext): string {
    const suppressBraces = context.suppressBraces;
    context.suppressBraces = true;
+   context.iteratorContext = "FOR_EXPRESSION";
    const key = this.resolveArg(context, FOR_EXPRESSION_KEY);
    const value = this.resolveArg(context, FOR_EXPRESSION_VALUE);
    const input = this.resolveArg(context, this.input);

Bug 2: OperatorExpression.resolve() uses truthiness check instead of undefined check

Line 325 uses this.right ? ... : undefined which treats all falsy values (0, false, "") as missing. The - operator branch (line 337) has the same bug.

Fix:

- const right = this.right ? this.resolveArg(context, this.right) : undefined;
+ const right = this.right !== undefined ? this.resolveArg(context, this.right) : undefined;
  case "-": {
-   if (right) {
+   if (right !== undefined) {
      // subtraction
      expr = `(${left} - ${right})`;
    } else {

Workarounds

  • Bug 1: Use raw HCL expression strings instead of forExpressionForList:
    containerRepoId: `\${[for c in ${data.fqn}.items : c.id if c.name == "foo"][0]}`,
  • Bug 2: Avoid using Op with falsy literal values. Use raw HCL strings for conditions instead.

Anything Else?

No response

References

Help Wanted

  • I'm interested in contributing a fix myself

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions