Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,99 @@ param vnetConfigs array
paramsObject.Should().HaveValueAtPath("parameters.sharedGroupName.value", "rg-search-foo-nonprod");
}

[TestMethod]
public async Task Build_params_for_expression_variable_should_succeed()
{
var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext);

_ = FileHelper.SaveResultFile(
TestContext,
"main.bicep",
"""
type FleetConfig = {
namePrefix: string
sku: string
capacity: int
clusteringPolicy: string
}

param testMatrix FleetConfig[]
""",
outputPath);

var paramsPath = FileHelper.SaveResultFile(
TestContext,
"main.bicepparam",
"""
using './main.bicep'

var matrix = [
{
namePrefix: 'e10impactx4'
sku: 'Enterprise_E10'
capacity: 4
}
{
namePrefix: 'e10impact'
sku: 'Enterprise_E10'
capacity: 2
}
]

var type1 = [for item in matrix: {
namePrefix: item.namePrefix
sku: item.sku
capacity: item.capacity
clusteringPolicy: 'EnterpriseCluster'
}]

var type2 = [for item in matrix: {
namePrefix: '${item.namePrefix}-ent'
sku: item.sku
capacity: item.capacity
clusteringPolicy: 'OSSCluster'
}]

param testMatrix = concat(type1, type2)
""",
outputPath);

var result = await Bicep(CreateDefaultSettings(), "build-params", paramsPath, "--stdout");

result.Should().Succeed();

var parametersStdout = result.Stdout.FromJson<BuildParamsStdout>();
var paramsObject = parametersStdout.parametersJson.FromJson<JToken>();
paramsObject.Should().HaveValueAtPath("parameters.testMatrix.value", JToken.Parse("""
[
{
"namePrefix": "e10impactx4",
"sku": "Enterprise_E10",
"capacity": 4,
"clusteringPolicy": "EnterpriseCluster"
},
{
"namePrefix": "e10impact",
"sku": "Enterprise_E10",
"capacity": 2,
"clusteringPolicy": "EnterpriseCluster"
},
{
"namePrefix": "e10impactx4-ent",
"sku": "Enterprise_E10",
"capacity": 4,
"clusteringPolicy": "OSSCluster"
},
{
"namePrefix": "e10impact-ent",
"sku": "Enterprise_E10",
"capacity": 2,
"clusteringPolicy": "OSSCluster"
}
]
"""));
}

[TestMethod]
public async Task Build_params_extends_variable_uses_base_params_not_overridden()
{
Expand Down
32 changes: 32 additions & 0 deletions src/Bicep.Core.UnitTests/Emit/ParameterAssignmentEvaluatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;

namespace Bicep.Core.UnitTests.Emit;

[TestClass]
public class ParameterAssignmentEvaluatorTests
{
[TestMethod]
public void BuildParams_ForExpressionVariable_EvaluatesToValue()
{
var services = new ServiceBuilder().WithEmptyAzResources();

var result = CompilationHelper.CompileParams(
services,
("main.bicep", "param p int[]"),
("parameters.bicepparam", """
using 'main.bicep'

var x = [for item in [1, 2]: item * 2]
param p = x
"""));

result.Should().NotHaveAnyDiagnostics();
result.Parameters.Should().HaveValueAtPath("parameters.p.value", JToken.Parse("[2, 4]"));
}
}
55 changes: 50 additions & 5 deletions src/Bicep.Core/Emit/ParameterAssignmentEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ namespace Bicep.Core.Emit;

public class ParameterAssignmentEvaluator
{
private sealed class ForLoopIndexRewriter : ExpressionRewriteVisitor
{
private readonly long index;

private ForLoopIndexRewriter(long index)
{
this.index = index;
}

public static Expression Rewrite(Expression expression, long index)
{
return new ForLoopIndexRewriter(index).Replace(expression);
}

public override Expression ReplaceCopyIndexExpression(CopyIndexExpression expression) => new IntegerLiteralExpression(expression.SourceSyntax, index);

public override Expression ReplaceForLoopExpression(ForLoopExpression expression) => expression;
}
private class ParameterAssignmentEvaluationContext : IEvaluationContext
{
private readonly TemplateExpressionEvaluationHelper evaluationHelper;
Expand Down Expand Up @@ -419,7 +437,7 @@ public Result EvaluateParameter(ParameterAssignmentSymbol parameter)

try
{
return Result.For(parameterConverter.ConvertExpression(intermediate).EvaluateExpression(context));
return Result.For(EvaluateExpression(parameterConverter, intermediate, context));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -478,7 +496,7 @@ public ImmutableDictionary<string, Result> EvaluateExtensionConfigAssignment(Ext
{
try
{
propertyResult = Result.For(converter.ConvertExpression(intermediate).EvaluateExpression(context));
propertyResult = Result.For(EvaluateExpression(converter, intermediate, context));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -542,7 +560,7 @@ private Result EvaluateVariable(VariableSymbol variable)
var variableConverter = GetConverterForVariable(variable);
var intermediate = variableConverter.ConvertToIntermediateExpression(variable.DeclaringVariable.Value);

return Result.For(variableConverter.ConvertExpression(intermediate).EvaluateExpression(context));
return Result.For(EvaluateExpression(variableConverter, intermediate, context));
}
catch (Exception ex)
{
Expand All @@ -560,7 +578,7 @@ private Result EvaluateSynthesizeVariableExpression(string name, Expression expr
{
var evalContext = GetExpressionEvaluationContextForModel(model);
var exprConverter = converterCache.GetOrAdd(model, m => new ExpressionConverter(new EmitterContext(m)));
return Result.For(exprConverter.ConvertExpression(expression).EvaluateExpression(evalContext));
return Result.For(EvaluateExpression(exprConverter, expression, evalContext));
}
catch (Exception e)
{
Expand Down Expand Up @@ -790,7 +808,7 @@ public ExpressionEvaluationResult EvaluateExpression(SyntaxBase expressionSyntax
{
var context = GetExpressionEvaluationContext();
var intermediate = converter.ConvertToIntermediateExpression(expressionSyntax);
var result = converter.ConvertExpression(intermediate).EvaluateExpression(context);
var result = EvaluateExpression(converter, intermediate, context);
return ExpressionEvaluationResult.For(result);
}
catch (Exception ex)
Expand All @@ -800,6 +818,33 @@ public ExpressionEvaluationResult EvaluateExpression(SyntaxBase expressionSyntax
}
}

private JToken EvaluateExpression(ExpressionConverter expressionConverter, Expression expression, ParameterAssignmentEvaluationContext context)
{
if (expression is ForLoopExpression forLoop)
{
return EvaluateForLoopExpression(expressionConverter, forLoop, context);
}

return expressionConverter.ConvertExpression(expression).EvaluateExpression(context);
}

private JToken EvaluateForLoopExpression(ExpressionConverter expressionConverter, ForLoopExpression forLoop, ParameterAssignmentEvaluationContext context)
{
var source = EvaluateExpression(expressionConverter, forLoop.Expression, context);
if (source is not JArray sourceArray)
{
throw new InvalidOperationException("For-expression source must be an array.");
}

var results = new JArray();
for (var i = 0; i < sourceArray.Count; i++)
{
results.Add(EvaluateExpression(expressionConverter, ForLoopIndexRewriter.Rewrite(forLoop.Body, i), context));
}

return results;
}

/// <summary>
/// Rewrites the external input function calls to use the externalInputs function with the index of the external input.
/// e.g. externalInput('sys.cli', 'foo') becomes externalInputs('0')
Expand Down
Loading