Skip to content

Nested function calls lose bindings when using extendable parameters #18765

@levimatheri

Description

@levimatheri

Bicep version
0.39.26

Describe the bug
When using extendable parameters feature and referencing the externalInput() function nested inside another function e.g. bool(externalInput()), the child bicepparam file fails to compile with the following diagnostic error:
BCP(338) Failed to evaluate parameter "": The template function 'externalInput' is not valid. Please see https://aka.ms/arm-functions for usage details.

To Reproduce

  1. Create a bicep file (main.bicep):
param trafficManagerEnabled bool
param endpointWeight int
  1. Create a parent bicepparam file (main.bicepparam):
using none
 
param trafficManagerEnabled =  bool(externalInput('scopeBinding', '__TRAFFICMANAGER_ENABLED__'))
  1. Create a child bicepparam file (parameters.bicepparam) that extends the main.bicepparam file:
using 'main.bicep'
 
extends 'main.bicepparam'
 
param endpointWeight = 50
  1. Enable the extendableParamFiles experimental feature flag in the bicepconfig.json file:
{
    "experimentalFeaturesEnabled": {
        "extendableParamFiles": true
    }
}
  1. Observe the error in VSCode or by running bicep build-params parameters.bicepparam:
Error BCP338: Failed to evaluate parameter "trafficManagerEnabled": The template function 'externalInput' is not valid. Please see https://aka.ms/arm-functions for usage details. [https://aka.ms/bicep/core-diagnostics#BCP338]

Additional context
After some debugging, I narrowed down the offending code to the following method:
If a function's argument is itself a function (like in bool(externalInput(...))), line 180 if-condition is skipped after evaluating line 195.
I think we should include FunctionCallSyntax in the if-check.

private void ProcessSyntaxForBinding(
SyntaxBase rootSyntax,
IBinder parentBinder,
IDictionary<SyntaxBase, Symbol> baseBindings)
{
var stack = new Stack<SyntaxBase>();
stack.Push(rootSyntax);
while (stack.Count > 0)
{
var current = stack.Pop();
if (current is VariableAccessSyntax || current == rootSyntax || current is PropertyAccessSyntax || current is ArrayAccessSyntax)
{
var parentSymbol = parentBinder.GetSymbolInfo(current);
if (parentSymbol is not null && !baseBindings.ContainsKey(current))
{
baseBindings[current] = parentSymbol;
}
}
var childNodes = current switch
{
ObjectSyntax obj => obj.Properties.Select(p => p.Value),
ArraySyntax arr => arr.Items.Select(i => i.Value),
PropertyAccessSyntax propAccess => [propAccess.BaseExpression],
ArrayAccessSyntax arrayAccess => [arrayAccess.BaseExpression, arrayAccess.IndexExpression],
FunctionCallSyntaxBase funcCall => funcCall.Arguments.Select(a => a.Expression),
ParenthesizedExpressionSyntax paren => [paren.Expression],
TernaryOperationSyntax ternary => [ternary.ConditionExpression, ternary.TrueExpression, ternary.FalseExpression],
BinaryOperationSyntax binary => [binary.LeftExpression, binary.RightExpression],
UnaryOperationSyntax unary => [unary.Expression],
NonNullAssertionSyntax nonNull => [nonNull.BaseExpression],
_ => []
};
foreach (var child in childNodes)
{
stack.Push(child);
}
}

cc @polatengin

Metadata

Metadata

Assignees

Type

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions