Expected Behavior
-
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> ].
-
Op.gt(someExpr, 0) (and other operators with falsy right-hand operands like 0, false, "") should render correctly, e.g. ${(length(...) > 0)}.
Actual Behavior
-
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.
-
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
- 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");
- Use
forExpressionForList to filter the list:
new Resource(this, "res", {
value: Fn.element(it.forExpressionForList(`val.id if val.name == "foo"`), 0),
});
- 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)
- Run
terraform plan and observe Duplicate object key error.
Bug 2: OperatorExpression drops falsy operands
- Use any operator with
0, false, or "" as the right operand:
Op.gt(Fn.lengthOf(someList), 0)
- 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
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
Expected Behavior
TerraformIterator.fromComplexList(list, "key").forExpressionForList(expr)should iterate the raw list directly (as it does when used viaTerraformDynamicExpression), producing[ for key, val in <list>: <expr> ].Op.gt(someExpr, 0)(and other operators with falsy right-hand operands like0,false,"") should render correctly, e.g.${(length(...) > 0)}.Actual Behavior
forExpressionForListon afromComplexListiterator always produces the map conversion{ for key, val in <list>: val.<keyAttr> => val }instead of using the raw list. This causes aDuplicate object keyerror when multiple items share the same key attribute value.Op.gt(someExpr, 0)renders as${(length(...) > undefined)}because0is falsy, causing a TerraformUnknown variable "undefined"error.Steps to Reproduce
Bug 1:
ForExpressionmissingiteratorContextforExpressionForListto filter the list:cdktn synthand observe the generated JSON uses the map conversion:terraform planand observeDuplicate object keyerror.Bug 2:
OperatorExpressiondrops falsy operands0,false, or""as the right operand:cdktn synthand observe the output contains> undefinedinstead 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 setiteratorContextDynamicListTerraformIterator._getForEachExpression()returns aLazythat checkscontext.iteratorContext— returning the raw list for"FOR_EXPRESSION"and the map conversion for the default case.TerraformDynamicExpression.resolve()correctly setscontext.iteratorContext = "FOR_EXPRESSION"before resolving, butForExpression.resolve()does not.Fix: Add
context.iteratorContext = "FOR_EXPRESSION"inForExpression.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 checkLine 325 uses
this.right ? ... : undefinedwhich treats all falsy values (0,false,"") as missing. The-operator branch (line 337) has the same bug.Fix:
case "-": { - if (right) { + if (right !== undefined) { // subtraction expr = `(${left} - ${right})`; } else {Workarounds
forExpressionForList:Opwith falsy literal values. Use raw HCL strings for conditions instead.Anything Else?
No response
References
Help Wanted
Community Note