Skip to content

Commit

Permalink
WIP2
Browse files Browse the repository at this point in the history
  • Loading branch information
miqm committed Jul 26, 2024
1 parent 6a61d62 commit 75ef620
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 23 deletions.
110 changes: 97 additions & 13 deletions src/Bicep.Core.IntegrationTests/Scenarios/NameofFunctionTests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using FluentAssertions.Execution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;

namespace Bicep.Core.IntegrationTests.Scenarios
{
Expand All @@ -23,13 +19,13 @@ public void NameofFunction_OnObjectProperty_ReturnsPropertyName()
var obj = {
prop: 'value'
}
var name = nameof(obj.prop)
output name string = nameof(obj.prop)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.variables['name']", "prop");
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "prop");
}
}

Expand All @@ -40,13 +36,28 @@ public void NameofFunction_OnVariable_ReturnsVariableName()
var obj = {
prop: 'value'
}
var name = nameof(obj)
output name string = nameof(obj)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.variables['name']", "obj");
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "obj");
}
}

[TestMethod]
public void NameofFunction_OnParameter_ReturnsParameterName()
{
var result = CompilationHelper.Compile("""
param test string
output name string = nameof(test)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "test");
}
}

Expand All @@ -58,13 +69,65 @@ public void NameofFunction_OnResource_ReturnsResourceSymbolicName()
name: 'storage123'
}

var name = nameof(myStorage)
output name string = nameof(myStorage)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "myStorage");
}
}

[TestMethod]
public void NameofFunction_OnChildResource_ReturnsResourceSymbolicName()
{
var result = CompilationHelper.Compile("""
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' = {
name: 'sql-server-name'

resource primaryDb 'databases' = {
name: 'primary'
}
}

output name string = nameof(sqlServer::primaryDb)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.variables['name']", "myStorage");
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "primaryDb");
}
}

[TestMethod]
public void NameofFunction_OnLoopedResource_ReturnsResourceSymbolicName()
{
var result = CompilationHelper.Compile("""

var dbs = [
'primary'
'secondary'
'tertiary'
]

resource sqlServer 'Microsoft.Sql/servers@2021-11-01' = {
name: 'sql-server-name'

@batchSize(1)
resource databases 'databases' = [for db in dbs: {
name: db
}]
}

output name string = nameof(sqlServer::databases)
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", "databases");
}
}

Expand All @@ -81,14 +144,35 @@ public void NameofFunction_OnResourceProperty_ReturnsResourcePropertyName(string
name: 'storage123'
}

var name = nameof(myStorage.{{resourceProperty}})
output name string = nameof(myStorage.{{resourceProperty}})

""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.variables['name']", expectedValue);
result.Template.Should().HaveValueAtPath("$.outputs['name'].value", expectedValue);
}
}

[TestMethod]
public void UsingNameofFunction_ShouldNotGenerateDependsOnEntries()
{
var result = CompilationHelper.Compile("""
resource myStorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {
name: 'storage123'
}

resource sqlServer 'Microsoft.Sql/servers@2021-11-01' = {
name: nameof(myStorage)
}
""");

using (new AssertionScope())
{
result.Should().NotHaveAnyCompilationBlockingDiagnostics();
result.Template.Should().HaveValueAtPath("$.resources[?(@.type == 'Microsoft.Sql/servers')].name", "myStorage", "Nameof function should emit static string during compilation");
result.Template.Should().NotHaveValueAtPath("$.resources[?(@.type == 'Microsoft.Sql/servers')].dependsOn", "Depends on should not be generated for nameof function usage");
}
}
}
Expand Down
21 changes: 15 additions & 6 deletions src/Bicep.Core/Emit/ForSyntaxValidatorVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private enum PropertyLoopCapability
private bool insideTopLevelDependsOn = false;

private bool insideProperties = false;
private bool insideNameOfFunction = false;

private ForSyntaxValidatorVisitor(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter)
{
Expand Down Expand Up @@ -251,6 +252,13 @@ public override void VisitResourceAccessSyntax(ResourceAccessSyntax syntax)
base.VisitResourceAccessSyntax(syntax);
}

public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax)
{
insideNameOfFunction = syntax.Name.IdentifierName == LanguageConstants.NameofFunctionName;
base.VisitFunctionCallSyntax(syntax);
insideNameOfFunction = false;
}

protected override void VisitInternal(SyntaxBase node)
{
var previousIsPropertyLoopPotentiallyAllowed = this.isPropertyLoopPotentiallyAllowed;
Expand Down Expand Up @@ -285,7 +293,7 @@ private void ValidateDirectAccessToResourceOrModuleCollection(SyntaxBase variabl
{
// we are inside a dependsOn property and the referenced symbol is a resource/module collection
var parent = this.semanticModel.Binder.GetParent(variableOrResourceAccessSyntax);
if (!this.insideTopLevelDependsOn && parent is not ArrayAccessSyntax)
if (!insideNameOfFunction && !this.insideTopLevelDependsOn && parent is not ArrayAccessSyntax)
{
// the parent is not array access, which means that someone is doing a direct reference to the collection
// NOTE(kylealbert): Direct access to resource collections:
Expand All @@ -295,11 +303,12 @@ private void ValidateDirectAccessToResourceOrModuleCollection(SyntaxBase variabl
// 1. Allowed in a variable declaration value
// 1. Allowed in an output value
var isValidResourceCollectionDirectAccessLocation =
this.semanticModel.Features.SymbolicNameCodegenEnabled
&& this.loopLevel == 0
&& (this.insideProperties
|| this.currentOutputDeclarationSyntax != null
|| (this.currentVariableDeclarationSyntax != null && this.variableAccessForInlineCheck == null));
(this.semanticModel.Features.SymbolicNameCodegenEnabled
&& this.loopLevel == 0
&& (this.insideProperties
|| this.currentOutputDeclarationSyntax != null
|| (this.currentVariableDeclarationSyntax != null && this.variableAccessForInlineCheck == null))
);

if (!isValidResourceCollectionDirectAccessLocation)
{
Expand Down
12 changes: 10 additions & 2 deletions src/Bicep.Core/Emit/ResourceDependencyVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class ResourceDependencyVisitor : AstVisitor
private Options? options;
private readonly IDictionary<DeclaredSymbol, HashSet<ResourceDependency>> resourceDependencies;
private DeclaredSymbol? currentDeclaration;
private bool insideNameofFunction;


public struct Options
Expand Down Expand Up @@ -154,7 +155,7 @@ private IEnumerable<ResourceDependency> GetResourceDependencies(DeclaredSymbol d

public override void VisitVariableAccessSyntax(VariableAccessSyntax syntax)
{
if (currentDeclaration is null)
if (currentDeclaration is null || insideNameofFunction)
{
return;
}
Expand Down Expand Up @@ -193,7 +194,7 @@ public override void VisitVariableAccessSyntax(VariableAccessSyntax syntax)

public override void VisitResourceAccessSyntax(ResourceAccessSyntax syntax)
{
if (currentDeclaration is null)
if (currentDeclaration is null || insideNameofFunction)
{
return;
}
Expand Down Expand Up @@ -282,6 +283,13 @@ public override void VisitObjectPropertySyntax(ObjectPropertySyntax propertySynt
base.VisitObjectPropertySyntax(propertySyntax);
}

public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax)
{
insideNameofFunction = syntax.Name.IdentifierName == LanguageConstants.NameofFunctionName;
base.VisitFunctionCallSyntax(syntax);
insideNameofFunction = false;
}

private bool IsTopLevelPropertyOfCurrentDeclaration(ObjectPropertySyntax propertySyntax)
{
SyntaxBase? declaringSyntax = this.currentDeclaration switch
Expand Down
2 changes: 2 additions & 0 deletions src/Bicep.Core/LanguageConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public static class LanguageConstants
public const string StringHoleClose = "}";

public const string AnyFunction = "any";
public const string NameofFunctionName = "nameof";

public static readonly TypeSymbol Any = new AnyType();
public static readonly TypeSymbol Never = new UnionType("never", []);

Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Core/Semantics/Namespaces/SystemNamespaceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ TypeSymbol type when TypeCollapser.TryCollapse(type) is TypeSymbol collapsed =>
}, LanguageConstants.Object)
.Build();

yield return new FunctionOverloadBuilder("nameof")
yield return new FunctionOverloadBuilder(LanguageConstants.NameofFunctionName)
.WithGenericDescription("Returns the name of the specified parameter, variable, or resource.")
.WithRequiredParameter("symbol", LanguageConstants.Any, "The parameter, variable, or resource to retrieve the name of.")
.WithReturnResultBuilder((model, diagnostics, call, argumentTypes) =>
Expand Down
1 change: 1 addition & 0 deletions src/Bicep.Core/Syntax/AstVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ public override void VisitParenthesizedExpressionSyntax(ParenthesizedExpressionS

public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax)
{

this.Visit(syntax.Name);
this.VisitNodes(syntax.Children);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ public override void VisitResourceAccessSyntax(ResourceAccessSyntax syntax)
public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax)
{
this.FlagIfFunctionRequiresInlining(syntax);

base.VisitFunctionCallSyntax(syntax);
}

Expand Down
13 changes: 13 additions & 0 deletions src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ public DeployTimeConstantViolationVisitor(

protected ResourceTypeResolver ResourceTypeResolver { get; }

private bool insideNameofFunction;

protected void FlagDeployTimeConstantViolation(SyntaxBase errorSyntax, DeclaredSymbol? accessedSymbol = null, ObjectType? accessedObjectType = null, IEnumerable<string>? variableDependencyChain = null, string? violatingPropertyName = null)
{
if (insideNameofFunction)
{
return;
}
var accessedSymbolName = accessedSymbol?.Name;
var accessiblePropertyNames = GetAccessiblePropertyNames(accessedSymbol, accessedObjectType);

Expand Down Expand Up @@ -95,5 +101,12 @@ ForSyntax forSyntax when ErrorSyntaxInForBodyOfVariable(forSyntax, errorSyntax)

return accessiblePropertyNames;
}

public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax)
{
insideNameofFunction = syntax.Name.IdentifierName == LanguageConstants.NameofFunctionName;
base.VisitFunctionCallSyntax(syntax);
insideNameofFunction = false;
}
}
}

0 comments on commit 75ef620

Please sign in to comment.