From 23ebbef0450e7c1c80a2b55ac9fa1c1fd8b7c011 Mon Sep 17 00:00:00 2001 From: Anthony Martin <38542602+anthony-c-martin@users.noreply.github.com> Date: Mon, 1 May 2023 18:23:38 -0400 Subject: [PATCH] Support for user defined functions (#10465) See https://github.com/Azure/bicep/blob/ant/exp/func/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep for a syntax example. Notes/limitations: * UDFs in JSON require declaring the return type, so I've modified the spec to require the user to declare the return type. * User-defined types in UDF params & outputs are unsupported. * UDFs are currently quite limited - they can't call UDFs, or access outer scoped variables. * This is currently behind an experimental feature flag ("userDefinedFunctions") Closes #447 Closes #9239 ###### Microsoft Reviewers: [Open in CodeFlow](https://portal.fabricbot.ms/api/codeflow?pullrequest=https://github.com/Azure/bicep/pull/10465) --- docs/grammar.md | 14 +- .../PrettyPrint/PrettyPrinterTests.cs | 1 - .../Semantics/SemanticModelTests.cs | 2 + .../UserDefinedFunctionTests.cs | 129 +++++ src/Bicep.Core.Samples/DataSets.cs | 4 + .../Files/Functions_LF/bicepconfig.json | 5 + .../Files/Functions_LF/main.bicep | 20 + .../Files/Functions_LF/main.diagnostics.bicep | 21 + .../Files/Functions_LF/main.formatted.bicep | 20 + .../Files/Functions_LF/main.ir.bicep | 69 +++ .../Files/Functions_LF/main.json | 104 ++++ .../Files/Functions_LF/main.sourcemap.bicep | 104 ++++ .../Functions_LF/main.symbolicnames.json | 106 ++++ .../Files/Functions_LF/main.symbols.bicep | 36 ++ .../Files/Functions_LF/main.syntax.bicep | 279 ++++++++++ .../Files/Functions_LF/main.tokens.bicep | 150 ++++++ .../InvalidFunctions_LF/bicepconfig.json | 6 + .../Files/InvalidFunctions_LF/main.bicep | 35 ++ .../main.diagnostics.bicep | 69 +++ .../InvalidFunctions_LF/main.formatted.bicep | 35 ++ .../InvalidFunctions_LF/main.symbols.bicep | 69 +++ .../InvalidFunctions_LF/main.syntax.bicep | 478 ++++++++++++++++++ .../InvalidFunctions_LF/main.tokens.bicep | 258 ++++++++++ .../InvalidModules_LF/main.diagnostics.bicep | 11 + .../InvalidModules_LF/main.symbols.bicep | 2 +- .../ConfigurationManagerTests.cs | 12 +- .../Features/FeatureProviderOverrides.cs | 4 +- .../Features/OverriddenFeatureProvider.cs | 2 + .../Linter/Rules/NoHardcodedLocationRule.cs | 2 +- .../Linter/Rules/UseStableVMImageRule.cs | 2 +- .../ExperimentalFeaturesEnabled.cs | 3 +- .../Diagnostics/DiagnosticBuilder.cs | 25 +- src/Bicep.Core/Emit/EmitConstants.cs | 9 + .../Emit/EmitLimitationCalculator.cs | 39 ++ src/Bicep.Core/Emit/ExpressionConverter.cs | 9 + src/Bicep.Core/Emit/TemplateWriter.cs | 53 ++ src/Bicep.Core/Features/FeatureProvider.cs | 2 + src/Bicep.Core/Features/IFeatureProvider.cs | 2 + src/Bicep.Core/Intermediate/Expression.cs | 32 +- .../Intermediate/ExpressionBuilder.cs | 43 +- .../Intermediate/ExpressionRewriteVisitor.cs | 21 +- .../Intermediate/ExpressionVisitor.cs | 11 + .../Intermediate/IExpressionVisitor.cs | 4 + src/Bicep.Core/LanguageConstants.cs | 1 + src/Bicep.Core/Parsing/BaseParser.cs | 20 + src/Bicep.Core/Parsing/Parser.cs | 10 + .../PrettyPrint/DocumentBuildVisitor.cs | 49 +- src/Bicep.Core/Semantics/Binder.cs | 27 +- .../Semantics/DeclarationVisitor.cs | 73 ++- .../Semantics/DeclaredFunctionSymbol.cs | 62 +++ src/Bicep.Core/Semantics/FileSymbol.cs | 35 +- src/Bicep.Core/Semantics/FunctionSymbol.cs | 2 +- src/Bicep.Core/Semantics/IFunctionSymbol.cs | 15 + src/Bicep.Core/Semantics/ILanguageScope.cs | 2 + src/Bicep.Core/Semantics/ISymbolContext.cs | 2 + src/Bicep.Core/Semantics/LocalScope.cs | 9 +- .../Semantics/LocalVariableSymbol.cs | 10 +- .../Semantics/NameBindingVisitor.cs | 49 +- .../Semantics/Namespaces/NamespaceResolver.cs | 5 +- src/Bicep.Core/Semantics/ScopeResolution.cs | 17 + .../Semantics/SemanticDiagnosticVisitor.cs | 6 + .../Semantics/SemanticModelHelper.cs | 6 +- src/Bicep.Core/Semantics/SymbolContext.cs | 26 +- src/Bicep.Core/Semantics/SymbolExtensions.cs | 2 +- src/Bicep.Core/Semantics/SymbolValidator.cs | 2 +- src/Bicep.Core/Semantics/SymbolVisitor.cs | 5 + src/Bicep.Core/Semantics/VariableSymbol.cs | 5 +- src/Bicep.Core/Syntax/AstVisitor.cs | 25 + src/Bicep.Core/Syntax/CstVisitor.cs | 29 ++ .../Syntax/FunctionDeclarationSyntax.cs | 32 ++ src/Bicep.Core/Syntax/ISymbolNameSource.cs | 5 - src/Bicep.Core/Syntax/ISyntaxVisitor.cs | 8 + src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs | 64 ++- src/Bicep.Core/Syntax/SyntaxVisitor.cs | 8 + src/Bicep.Core/Syntax/TypedLambdaSyntax.cs | 36 ++ .../Syntax/TypedLocalVariableSyntax.cs | 24 + .../Syntax/TypedVariableBlockSyntax.cs | 33 ++ .../Syntax/VariableDeclarationSyntax.cs | 14 - .../TypeSystem/DeclaredTypeManager.cs | 31 +- .../DeployTimeConstantContainerVisitor.cs | 7 + .../TypeSystem/DeployTimeConstantValidator.cs | 3 +- .../DeployTimeConstantViolationVisitor.cs | 3 +- src/Bicep.Core/TypeSystem/FunctionFlags.cs | 7 +- src/Bicep.Core/TypeSystem/FunctionResolver.cs | 2 +- .../TypeSystem/TypeAssignmentVisitor.cs | 65 ++- src/Bicep.Core/TypeSystem/TypeValidator.cs | 2 +- src/Bicep.Decompiler/TemplateConverter.cs | 2 + .../CompletionTests.cs | 145 +++++- .../Helpers/IntegrationTestHelper.cs | 13 +- .../Helpers/ServerRequestHelper.cs | 21 + .../HoverTests.cs | 54 +- .../RenameSymbolTests.cs | 2 +- .../SignatureHelpTests.cs | 33 +- .../Completions/BicepCompletionContext.cs | 30 +- .../Completions/BicepCompletionContextKind.cs | 12 +- .../Completions/BicepCompletionProvider.cs | 36 +- .../Completions/SyntaxMatcher.cs | 4 +- .../Handlers/BicepDocumentSymbolHandler.cs | 2 + .../Handlers/BicepHoverHandler.cs | 11 +- .../Handlers/BicepSignatureHelpHandler.cs | 4 +- src/highlightjs/src/bicep.ts | 1 + src/highlightjs/test/baselines/basic.html | 4 +- .../test/baselines/bicepconfig.json | 1 + .../test/baselines/functions.bicep | 15 + src/highlightjs/test/baselines/functions.html | 30 ++ src/monarch/src/bicep.ts | 1 + src/monarch/test/baselines/basic.html | 4 +- src/monarch/test/baselines/bicepconfig.json | 1 + src/monarch/test/baselines/functions.bicep | 15 + src/monarch/test/baselines/functions.html | 31 ++ src/textmate/bicep.tmlanguage | 2 +- src/textmate/src/bicep.ts | 1 + src/textmate/test/baselines/basic.html | 4 +- src/textmate/test/baselines/bicepconfig.json | 1 + src/textmate/test/baselines/functions.bicep | 15 + src/textmate/test/baselines/functions.html | 31 ++ .../schemas/bicepconfig.schema.json | 3 + 117 files changed, 3439 insertions(+), 230 deletions(-) create mode 100644 src/Bicep.Core.IntegrationTests/UserDefinedFunctionTests.cs create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/bicepconfig.json create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.diagnostics.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.formatted.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.ir.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.json create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.sourcemap.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.symbolicnames.json create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.symbols.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.syntax.bicep create mode 100644 src/Bicep.Core.Samples/Files/Functions_LF/main.tokens.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/bicepconfig.json create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.diagnostics.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.formatted.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.symbols.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.syntax.bicep create mode 100644 src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.tokens.bicep create mode 100644 src/Bicep.Core/Emit/EmitConstants.cs create mode 100644 src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs create mode 100644 src/Bicep.Core/Semantics/IFunctionSymbol.cs create mode 100644 src/Bicep.Core/Semantics/ScopeResolution.cs create mode 100644 src/Bicep.Core/Syntax/FunctionDeclarationSyntax.cs create mode 100644 src/Bicep.Core/Syntax/TypedLambdaSyntax.cs create mode 100644 src/Bicep.Core/Syntax/TypedLocalVariableSyntax.cs create mode 100644 src/Bicep.Core/Syntax/TypedVariableBlockSyntax.cs create mode 100644 src/highlightjs/test/baselines/functions.bicep create mode 100644 src/highlightjs/test/baselines/functions.html create mode 100644 src/monarch/test/baselines/functions.bicep create mode 100644 src/monarch/test/baselines/functions.html create mode 100644 src/textmate/test/baselines/functions.bicep create mode 100644 src/textmate/test/baselines/functions.html diff --git a/docs/grammar.md b/docs/grammar.md index 2f12f043343..48a0397959a 100644 --- a/docs/grammar.md +++ b/docs/grammar.md @@ -12,6 +12,7 @@ statement -> resourceDecl | moduleDecl | outputDecl | + functionDecl | NL targetScopeDecl -> "targetScope" "=" expression @@ -42,6 +43,8 @@ outputDecl -> decorator* "output" IDENTIFIER(name) "resource" interpString(type) "=" expression NL NL -> ("\n" | "\r")+ +functionDecl -> decorator* "func" IDENTIFIER(name) typedLambdaExpression NL + decorator -> "@" decoratorExpression NL disableNextLineDiagnosticsDirective-> #disable-next-line diagnosticCode1 diagnosticCode2 diagnosticCode3 NL @@ -106,13 +109,18 @@ primaryExpression -> decoratorExpression -> functionCall | memberExpression "." functionCall -functionCall -> IDENTIFIER "(" argumentList? ")" - argumentList -> expression ("," expression)* +functionCall -> IDENTIFIER "(" argumentList? ")" parenthesizedExpression -> "(" expression ")" -lambdaExpression -> ( "(" argumentList? ")" | IDENTIFIER ) "=>" expression +localVariable -> IDENTIFIER +variableBlock -> "(" ( localVariable ("," localVariable)* )? ")" +lambdaExpression -> ( variableBlock | localVariable ) "=>" expression + +typedLocalVariable -> IDENTIFIER primaryTypeExpression +typedVariableBlock -> "(" ( typedLocalVariable ("," typedLocalVariable)* )? ")" +typedLambdaExpression -> typedVariableBlock primaryTypeExpression "=>" expression ifCondition -> "if" parenthesizedExpression object diff --git a/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterTests.cs b/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterTests.cs index 96357b7c7ba..b7b9c845546 100644 --- a/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterTests.cs +++ b/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterTests.cs @@ -74,7 +74,6 @@ public void PrintProgram_PrintTwice_ReturnsConsistentResults(DataSet dataSet) var newSyntaxErrorMessages = newSyntaxErrors.Select(d => d.Message); // Diagnostic messages should remain the same after formatting. - syntaxErrors.Should().HaveSameCount(newSyntaxErrorMessages); newSyntaxErrorMessages.Should().BeEquivalentTo(syntaxErrroMessages); // Normalize formatting diff --git a/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs b/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs index 0f6ea222796..4ebf819aead 100644 --- a/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs +++ b/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs @@ -129,6 +129,7 @@ s is ResourceSymbol || s is ModuleSymbol || s is OutputSymbol || s is FunctionSymbol || + s is DeclaredFunctionSymbol || s is ImportedNamespaceSymbol || s is BuiltInNamespaceSymbol || s is LocalVariableSymbol); @@ -147,6 +148,7 @@ s is ResourceSymbol || s is ModuleSymbol || s is OutputSymbol || s is FunctionSymbol || + s is DeclaredFunctionSymbol || s is ImportedNamespaceSymbol || s is BuiltInNamespaceSymbol || s is LocalVariableSymbol); diff --git a/src/Bicep.Core.IntegrationTests/UserDefinedFunctionTests.cs b/src/Bicep.Core.IntegrationTests/UserDefinedFunctionTests.cs new file mode 100644 index 00000000000..0dc4ba7f919 --- /dev/null +++ b/src/Bicep.Core.IntegrationTests/UserDefinedFunctionTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; +using Bicep.Core.Diagnostics; +using Bicep.Core.UnitTests; +using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Core.IntegrationTests; + +[TestClass] +public class UserDefinedFunctionTests +{ + private ServiceBuilder Services => new ServiceBuilder() + .WithFeatureOverrides(new(TestContext, UserDefinedFunctionsEnabled: true)); + + [NotNull] public TestContext? TestContext { get; set; } + + [TestMethod] + public void User_defined_functions_basic_case() + { + var result = CompilationHelper.Compile(Services, @" +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') +"); + + result.Should().NotHaveAnyDiagnostics(); + var evaluated = TemplateEvaluator.Evaluate(result.Template); + + evaluated.Should().HaveValueAtPath("$.outputs['foo'].value", "https://google.com/search"); + } + + [TestMethod] + public void Outer_scope_symbolic_references_are_blocked() + { + var result = CompilationHelper.Compile(Services, @" +param foo string +var bar = 'abc' +func getBaz() string => 'baz' + +func testFunc(baz string) string => '${foo}-${bar}-${baz}-${getBaz()}' +"); + + result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new [] { + ("BCP057", DiagnosticLevel.Error, """The name "foo" does not exist in the current context."""), + ("BCP057", DiagnosticLevel.Error, """The name "bar" does not exist in the current context."""), + ("BCP057", DiagnosticLevel.Error, """The name "getBaz" does not exist in the current context."""), + }); + } + + [TestMethod] + public void Functions_can_have_descriptions_applied() + { + var result = CompilationHelper.Compile(Services, @" +@description('Returns foo') +func returnFoo() string => 'foo' + +output outputFoo string = returnFoo() +"); + + result.Should().NotHaveAnyDiagnostics(); + var evaluated = TemplateEvaluator.Evaluate(result.Template); + + evaluated.Should().HaveValueAtPath("$.outputs['outputFoo'].value", "foo"); + } + + [TestMethod] + public void User_defined_functions_cannot_reference_each_other() + { + var result = CompilationHelper.Compile(Services, @" +func getAbc() string => 'abc' +func getAbcDef() string => '${getAbc()}def' +"); + + result.Should().HaveDiagnostics(new [] { + ("BCP057", DiagnosticLevel.Error, "The name \"getAbc\" does not exist in the current context."), + }); + } + + [TestMethod] + public void User_defined_functions_unsupported_custom_types() + { + var services = new ServiceBuilder().WithFeatureOverrides(new(UserDefinedTypesEnabled: true, UserDefinedFunctionsEnabled: true)); + + var result = CompilationHelper.Compile(services, @" +func getAOrB(aOrB ('a' | 'b')) bool => (aOrB == 'a') +"); + + result.Should().HaveDiagnostics(new [] { + ("BCP342", DiagnosticLevel.Error, "User-defined types are not supported in user-defined function parameters or outputs."), + }); + + result = CompilationHelper.Compile(services, @" +func getAOrB(aOrB bool) ('a' | 'b') => aOrB ? 'a' : 'b' +"); + + result.Should().HaveDiagnostics(new [] { + ("BCP342", DiagnosticLevel.Error, "User-defined types are not supported in user-defined function parameters or outputs."), + }); + } + + [TestMethod] + public void User_defined_functions_unsupported_runtime_functions() + { + var result = CompilationHelper.Compile(Services, @" +func useRuntimeFunction() string => reference('foo').bar +"); + + result.Should().HaveDiagnostics(new [] { + ("BCP341", DiagnosticLevel.Error, "This expression is being used inside a function declaration, which requires a value that can be calculated at the start of the deployment."), + }); + } + + [TestMethod] + public void User_defined_functions_requires_experimental_feature_enabled() + { + var services = new ServiceBuilder().WithFeatureOverrides(new()); + + var result = CompilationHelper.Compile(services, @" +func useRuntimeFunction() string => 'test' +"); + + result.Should().HaveDiagnostics(new [] { + ("BCP343", DiagnosticLevel.Error, "Using a func declaration statement requires enabling EXPERIMENTAL feature \"UserDefinedFunctions\"."), + }); + } +} diff --git a/src/Bicep.Core.Samples/DataSets.cs b/src/Bicep.Core.Samples/DataSets.cs index 8affecba015..7878f6128a8 100644 --- a/src/Bicep.Core.Samples/DataSets.cs +++ b/src/Bicep.Core.Samples/DataSets.cs @@ -20,12 +20,16 @@ public static class DataSets public static DataSet Empty => CreateDataSet(); + public static DataSet Functions_LF => CreateDataSet(); + public static DataSet InvalidCycles_CRLF => CreateDataSet(); public static DataSet InvalidDisableNextLineDiagnosticsDirective_CRLF => CreateDataSet(); public static DataSet InvalidExpressions_LF => CreateDataSet(); + public static DataSet InvalidFunctions_LF => CreateDataSet(); + public static DataSet InvalidMetadata_CRLF => CreateDataSet(); public static DataSet InvalidOutputs_CRLF => CreateDataSet(); diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/bicepconfig.json b/src/Bicep.Core.Samples/Files/Functions_LF/bicepconfig.json new file mode 100644 index 00000000000..111be40be4a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/bicepconfig.json @@ -0,0 +1,5 @@ +{ + "experimentalFeaturesEnabled": { + "userDefinedFunctions": true + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep new file mode 100644 index 00000000000..4f0a9e30e39 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.bicep @@ -0,0 +1,20 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] + +func asdf(name string) array => [ + 'asdf' + name +] diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.diagnostics.bicep new file mode 100644 index 00000000000..ea247400e3b --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.diagnostics.bicep @@ -0,0 +1,21 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] + +func asdf(name string) array => [ + 'asdf' + name +] + diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.formatted.bicep new file mode 100644 index 00000000000..3f9726fce0a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.formatted.bicep @@ -0,0 +1,20 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map([ 'Evie', 'Casper' ], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] + +func asdf(name string) array => [ + 'asdf' + name +] diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.ir.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.ir.bicep new file mode 100644 index 00000000000..542f4eb3ed4 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.ir.bicep @@ -0,0 +1,69 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' +//@[000:503) ProgramExpression +//@[000:141) ├─DeclaredFunctionExpression { Name = buildUrl } +//@[013:141) | └─LambdaExpression +//@[066:141) | └─InterpolatedStringExpression +//@[069:093) | ├─TernaryExpression +//@[069:074) | | ├─LambdaVariableReferenceExpression { Variable = https } +//@[077:084) | | ├─StringLiteralExpression { Value = https } +//@[087:093) | | └─StringLiteralExpression { Value = http } +//@[099:107) | ├─LambdaVariableReferenceExpression { Variable = hostname } +//@[110:139) | └─TernaryExpression +//@[110:121) | ├─FunctionCallExpression { Name = empty } +//@[116:120) | | └─LambdaVariableReferenceExpression { Variable = path } +//@[124:126) | ├─StringLiteralExpression { Value = } +//@[129:139) | └─InterpolatedStringExpression +//@[133:137) | └─LambdaVariableReferenceExpression { Variable = path } + +output foo string = buildUrl(true, 'google.com', 'search') +//@[000:058) ├─DeclaredOutputExpression { Name = foo } +//@[020:058) | └─UserDefinedFunctionCallExpression { Name = buildUrl } +//@[029:033) | ├─BooleanLiteralExpression { Value = True } +//@[035:047) | ├─StringLiteralExpression { Value = google.com } +//@[049:057) | └─StringLiteralExpression { Value = search } + +func sayHello(name string) string => 'Hi ${name}!' +//@[000:050) ├─DeclaredFunctionExpression { Name = sayHello } +//@[013:050) | └─LambdaExpression +//@[037:050) | └─InterpolatedStringExpression +//@[043:047) | └─LambdaVariableReferenceExpression { Variable = name } + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) +//@[000:069) └─DeclaredOutputExpression { Name = hellos } +//@[022:069) └─FunctionCallExpression { Name = map } +//@[026:044) ├─ArrayExpression +//@[027:033) | ├─StringLiteralExpression { Value = Evie } +//@[035:043) | └─StringLiteralExpression { Value = Casper } +//@[046:068) └─LambdaExpression +//@[054:068) └─UserDefinedFunctionCallExpression { Name = sayHello } +//@[063:067) └─LambdaVariableReferenceExpression { Variable = name } + +func objReturnType(name string) object => { +//@[000:068) ├─DeclaredFunctionExpression { Name = objReturnType } +//@[018:068) | └─LambdaExpression +//@[042:068) | └─ObjectExpression + hello: 'Hi ${name}!' +//@[002:022) | └─ObjectPropertyExpression +//@[002:007) | ├─StringLiteralExpression { Value = hello } +//@[009:022) | └─InterpolatedStringExpression +//@[015:019) | └─LambdaVariableReferenceExpression { Variable = name } +} + +func arrayReturnType(name string) array => [ +//@[000:053) ├─DeclaredFunctionExpression { Name = arrayReturnType } +//@[020:053) | └─LambdaExpression +//@[043:053) | └─ArrayExpression + name +//@[002:006) | └─LambdaVariableReferenceExpression { Variable = name } +] + +func asdf(name string) array => [ +//@[000:051) ├─DeclaredFunctionExpression { Name = asdf } +//@[009:051) | └─LambdaExpression +//@[032:051) | └─ArrayExpression + 'asdf' +//@[002:008) | ├─StringLiteralExpression { Value = asdf } + name +//@[002:006) | └─LambdaVariableReferenceExpression { Variable = name } +] + diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.json b/src/Bicep.Core.Samples/Files/Functions_LF/main.json new file mode 100644 index 00000000000..07462fc0e7f --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "10926110834786320167" + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "buildUrl": { + "parameters": [ + { + "type": "bool", + "name": "https" + }, + { + "type": "string", + "name": "hostname" + }, + { + "type": "string", + "name": "path" + } + ], + "output": { + "type": "string", + "value": "[format('{0}://{1}{2}', if(parameters('https'), 'https', 'http'), parameters('hostname'), if(empty(parameters('path')), '', format('/{0}', parameters('path'))))]" + } + }, + "sayHello": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[format('Hi {0}!', parameters('name'))]" + } + }, + "objReturnType": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "object", + "value": { + "hello": "[format('Hi {0}!', parameters('name'))]" + } + } + }, + "arrayReturnType": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "array", + "value": [ + "[parameters('name')]" + ] + } + }, + "asdf": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "array", + "value": [ + "asdf", + "[parameters('name')]" + ] + } + } + } + } + ], + "resources": [], + "outputs": { + "foo": { + "type": "string", + "value": "[__bicep.buildUrl(true(), 'google.com', 'search')]" + }, + "hellos": { + "type": "array", + "value": "[map(createArray('Evie', 'Casper'), lambda('name', __bicep.sayHello(lambdaVariables('name'))))]" + } + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.sourcemap.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.sourcemap.bicep new file mode 100644 index 00000000000..dc6770ec77a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.sourcemap.bicep @@ -0,0 +1,104 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' +//@ "buildUrl": { +//@ "parameters": [ +//@ { +//@ "type": "bool", +//@ "name": "https" +//@ }, +//@ { +//@ "type": "string", +//@ "name": "hostname" +//@ }, +//@ { +//@ "type": "string", +//@ "name": "path" +//@ } +//@ ], +//@ "output": { +//@ "type": "string", +//@ "value": "[format('{0}://{1}{2}', if(parameters('https'), 'https', 'http'), parameters('hostname'), if(empty(parameters('path')), '', format('/{0}', parameters('path'))))]" +//@ } +//@ }, + +output foo string = buildUrl(true, 'google.com', 'search') +//@ "foo": { +//@ "type": "string", +//@ "value": "[__bicep.buildUrl(true(), 'google.com', 'search')]" +//@ }, + +func sayHello(name string) string => 'Hi ${name}!' +//@ "sayHello": { +//@ "parameters": [ +//@ { +//@ "type": "string", +//@ "name": "name" +//@ } +//@ ], +//@ "output": { +//@ "type": "string", +//@ "value": "[format('Hi {0}!', parameters('name'))]" +//@ } +//@ }, + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) +//@ "hellos": { +//@ "type": "array", +//@ "value": "[map(createArray('Evie', 'Casper'), lambda('name', __bicep.sayHello(lambdaVariables('name'))))]" +//@ } + +func objReturnType(name string) object => { +//@ "objReturnType": { +//@ "parameters": [ +//@ { +//@ "type": "string", +//@ "name": "name" +//@ } +//@ ], +//@ "output": { +//@ "type": "object", +//@ "value": { +//@ } +//@ } +//@ }, + hello: 'Hi ${name}!' +//@ "hello": "[format('Hi {0}!', parameters('name'))]" +} + +func arrayReturnType(name string) array => [ +//@ "arrayReturnType": { +//@ "parameters": [ +//@ { +//@ "type": "string", +//@ "name": "name" +//@ } +//@ ], +//@ "output": { +//@ "type": "array", +//@ "value": [ +//@ ] +//@ } +//@ }, + name +//@ "[parameters('name')]" +] + +func asdf(name string) array => [ +//@ "asdf": { +//@ "parameters": [ +//@ { +//@ "type": "string", +//@ "name": "name" +//@ } +//@ ], +//@ "output": { +//@ "type": "array", +//@ "value": [ +//@ ] +//@ } +//@ } + 'asdf' +//@ "asdf", + name +//@ "[parameters('name')]" +] + diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.symbolicnames.json b/src/Bicep.Core.Samples/Files/Functions_LF/main.symbolicnames.json new file mode 100644 index 00000000000..c3221d2cf45 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.symbolicnames.json @@ -0,0 +1,106 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "1.10-experimental", + "contentVersion": "1.0.0.0", + "metadata": { + "_EXPERIMENTAL_WARNING": "Symbolic name support in ARM is experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!", + "_generator": { + "name": "bicep", + "version": "dev", + "templateHash": "834592582032208770" + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "buildUrl": { + "parameters": [ + { + "type": "bool", + "name": "https" + }, + { + "type": "string", + "name": "hostname" + }, + { + "type": "string", + "name": "path" + } + ], + "output": { + "type": "string", + "value": "[format('{0}://{1}{2}', if(parameters('https'), 'https', 'http'), parameters('hostname'), if(empty(parameters('path')), '', format('/{0}', parameters('path'))))]" + } + }, + "sayHello": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[format('Hi {0}!', parameters('name'))]" + } + }, + "objReturnType": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "object", + "value": { + "hello": "[format('Hi {0}!', parameters('name'))]" + } + } + }, + "arrayReturnType": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "array", + "value": [ + "[parameters('name')]" + ] + } + }, + "asdf": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "array", + "value": [ + "asdf", + "[parameters('name')]" + ] + } + } + } + } + ], + "resources": {}, + "outputs": { + "foo": { + "type": "string", + "value": "[__bicep.buildUrl(true(), 'google.com', 'search')]" + }, + "hellos": { + "type": "array", + "value": "[map(createArray('Evie', 'Casper'), lambda('name', __bicep.sayHello(lambdaVariables('name'))))]" + } + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.symbols.bicep new file mode 100644 index 00000000000..57c91053e9d --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.symbols.bicep @@ -0,0 +1,36 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' +//@[14:19) Local https. Type: bool. Declaration start char: 14, length: 10 +//@[26:34) Local hostname. Type: string. Declaration start char: 26, length: 15 +//@[43:47) Local path. Type: string. Declaration start char: 43, length: 11 +//@[05:13) Function buildUrl. Type: (bool, string, string) => string. Declaration start char: 0, length: 141 + +output foo string = buildUrl(true, 'google.com', 'search') +//@[07:10) Output foo. Type: string. Declaration start char: 0, length: 58 + +func sayHello(name string) string => 'Hi ${name}!' +//@[14:18) Local name. Type: string. Declaration start char: 14, length: 11 +//@[05:13) Function sayHello. Type: string => string. Declaration start char: 0, length: 50 + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) +//@[46:50) Local name. Type: 'Casper' | 'Evie'. Declaration start char: 46, length: 4 +//@[07:13) Output hellos. Type: array. Declaration start char: 0, length: 69 + +func objReturnType(name string) object => { +//@[19:23) Local name. Type: string. Declaration start char: 19, length: 11 +//@[05:18) Function objReturnType. Type: string => object. Declaration start char: 0, length: 68 + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ +//@[21:25) Local name. Type: string. Declaration start char: 21, length: 11 +//@[05:20) Function arrayReturnType. Type: string => [string]. Declaration start char: 0, length: 53 + name +] + +func asdf(name string) array => [ +//@[10:14) Local name. Type: string. Declaration start char: 10, length: 11 +//@[05:09) Function asdf. Type: string => ['asdf', string]. Declaration start char: 0, length: 51 + 'asdf' + name +] + diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.syntax.bicep new file mode 100644 index 00000000000..7ec00a06f1e --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.syntax.bicep @@ -0,0 +1,279 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' +//@[000:503) ProgramSyntax +//@[000:141) ├─FunctionDeclarationSyntax +//@[000:004) | ├─Token(Identifier) |func| +//@[005:013) | ├─IdentifierSyntax +//@[005:013) | | └─Token(Identifier) |buildUrl| +//@[013:141) | └─TypedLambdaSyntax +//@[013:055) | ├─TypedVariableBlockSyntax +//@[013:014) | | ├─Token(LeftParen) |(| +//@[014:024) | | ├─TypedLocalVariableSyntax +//@[014:019) | | | ├─IdentifierSyntax +//@[014:019) | | | | └─Token(Identifier) |https| +//@[020:024) | | | └─VariableAccessSyntax +//@[020:024) | | | └─IdentifierSyntax +//@[020:024) | | | └─Token(Identifier) |bool| +//@[024:025) | | ├─Token(Comma) |,| +//@[026:041) | | ├─TypedLocalVariableSyntax +//@[026:034) | | | ├─IdentifierSyntax +//@[026:034) | | | | └─Token(Identifier) |hostname| +//@[035:041) | | | └─VariableAccessSyntax +//@[035:041) | | | └─IdentifierSyntax +//@[035:041) | | | └─Token(Identifier) |string| +//@[041:042) | | ├─Token(Comma) |,| +//@[043:054) | | ├─TypedLocalVariableSyntax +//@[043:047) | | | ├─IdentifierSyntax +//@[043:047) | | | | └─Token(Identifier) |path| +//@[048:054) | | | └─VariableAccessSyntax +//@[048:054) | | | └─IdentifierSyntax +//@[048:054) | | | └─Token(Identifier) |string| +//@[054:055) | | └─Token(RightParen) |)| +//@[056:062) | ├─VariableAccessSyntax +//@[056:062) | | └─IdentifierSyntax +//@[056:062) | | └─Token(Identifier) |string| +//@[063:065) | ├─Token(Arrow) |=>| +//@[066:141) | └─StringSyntax +//@[066:069) | ├─Token(StringLeftPiece) |'${| +//@[069:093) | ├─TernaryOperationSyntax +//@[069:074) | | ├─VariableAccessSyntax +//@[069:074) | | | └─IdentifierSyntax +//@[069:074) | | | └─Token(Identifier) |https| +//@[075:076) | | ├─Token(Question) |?| +//@[077:084) | | ├─StringSyntax +//@[077:084) | | | └─Token(StringComplete) |'https'| +//@[085:086) | | ├─Token(Colon) |:| +//@[087:093) | | └─StringSyntax +//@[087:093) | | └─Token(StringComplete) |'http'| +//@[093:099) | ├─Token(StringMiddlePiece) |}://${| +//@[099:107) | ├─VariableAccessSyntax +//@[099:107) | | └─IdentifierSyntax +//@[099:107) | | └─Token(Identifier) |hostname| +//@[107:110) | ├─Token(StringMiddlePiece) |}${| +//@[110:139) | ├─TernaryOperationSyntax +//@[110:121) | | ├─FunctionCallSyntax +//@[110:115) | | | ├─IdentifierSyntax +//@[110:115) | | | | └─Token(Identifier) |empty| +//@[115:116) | | | ├─Token(LeftParen) |(| +//@[116:120) | | | ├─FunctionArgumentSyntax +//@[116:120) | | | | └─VariableAccessSyntax +//@[116:120) | | | | └─IdentifierSyntax +//@[116:120) | | | | └─Token(Identifier) |path| +//@[120:121) | | | └─Token(RightParen) |)| +//@[122:123) | | ├─Token(Question) |?| +//@[124:126) | | ├─StringSyntax +//@[124:126) | | | └─Token(StringComplete) |''| +//@[127:128) | | ├─Token(Colon) |:| +//@[129:139) | | └─StringSyntax +//@[129:133) | | ├─Token(StringLeftPiece) |'/${| +//@[133:137) | | ├─VariableAccessSyntax +//@[133:137) | | | └─IdentifierSyntax +//@[133:137) | | | └─Token(Identifier) |path| +//@[137:139) | | └─Token(StringRightPiece) |}'| +//@[139:141) | └─Token(StringRightPiece) |}'| +//@[141:143) ├─Token(NewLine) |\n\n| + +output foo string = buildUrl(true, 'google.com', 'search') +//@[000:058) ├─OutputDeclarationSyntax +//@[000:006) | ├─Token(Identifier) |output| +//@[007:010) | ├─IdentifierSyntax +//@[007:010) | | └─Token(Identifier) |foo| +//@[011:017) | ├─VariableAccessSyntax +//@[011:017) | | └─IdentifierSyntax +//@[011:017) | | └─Token(Identifier) |string| +//@[018:019) | ├─Token(Assignment) |=| +//@[020:058) | └─FunctionCallSyntax +//@[020:028) | ├─IdentifierSyntax +//@[020:028) | | └─Token(Identifier) |buildUrl| +//@[028:029) | ├─Token(LeftParen) |(| +//@[029:033) | ├─FunctionArgumentSyntax +//@[029:033) | | └─BooleanLiteralSyntax +//@[029:033) | | └─Token(TrueKeyword) |true| +//@[033:034) | ├─Token(Comma) |,| +//@[035:047) | ├─FunctionArgumentSyntax +//@[035:047) | | └─StringSyntax +//@[035:047) | | └─Token(StringComplete) |'google.com'| +//@[047:048) | ├─Token(Comma) |,| +//@[049:057) | ├─FunctionArgumentSyntax +//@[049:057) | | └─StringSyntax +//@[049:057) | | └─Token(StringComplete) |'search'| +//@[057:058) | └─Token(RightParen) |)| +//@[058:060) ├─Token(NewLine) |\n\n| + +func sayHello(name string) string => 'Hi ${name}!' +//@[000:050) ├─FunctionDeclarationSyntax +//@[000:004) | ├─Token(Identifier) |func| +//@[005:013) | ├─IdentifierSyntax +//@[005:013) | | └─Token(Identifier) |sayHello| +//@[013:050) | └─TypedLambdaSyntax +//@[013:026) | ├─TypedVariableBlockSyntax +//@[013:014) | | ├─Token(LeftParen) |(| +//@[014:025) | | ├─TypedLocalVariableSyntax +//@[014:018) | | | ├─IdentifierSyntax +//@[014:018) | | | | └─Token(Identifier) |name| +//@[019:025) | | | └─VariableAccessSyntax +//@[019:025) | | | └─IdentifierSyntax +//@[019:025) | | | └─Token(Identifier) |string| +//@[025:026) | | └─Token(RightParen) |)| +//@[027:033) | ├─VariableAccessSyntax +//@[027:033) | | └─IdentifierSyntax +//@[027:033) | | └─Token(Identifier) |string| +//@[034:036) | ├─Token(Arrow) |=>| +//@[037:050) | └─StringSyntax +//@[037:043) | ├─Token(StringLeftPiece) |'Hi ${| +//@[043:047) | ├─VariableAccessSyntax +//@[043:047) | | └─IdentifierSyntax +//@[043:047) | | └─Token(Identifier) |name| +//@[047:050) | └─Token(StringRightPiece) |}!'| +//@[050:052) ├─Token(NewLine) |\n\n| + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) +//@[000:069) ├─OutputDeclarationSyntax +//@[000:006) | ├─Token(Identifier) |output| +//@[007:013) | ├─IdentifierSyntax +//@[007:013) | | └─Token(Identifier) |hellos| +//@[014:019) | ├─VariableAccessSyntax +//@[014:019) | | └─IdentifierSyntax +//@[014:019) | | └─Token(Identifier) |array| +//@[020:021) | ├─Token(Assignment) |=| +//@[022:069) | └─FunctionCallSyntax +//@[022:025) | ├─IdentifierSyntax +//@[022:025) | | └─Token(Identifier) |map| +//@[025:026) | ├─Token(LeftParen) |(| +//@[026:044) | ├─FunctionArgumentSyntax +//@[026:044) | | └─ArraySyntax +//@[026:027) | | ├─Token(LeftSquare) |[| +//@[027:033) | | ├─ArrayItemSyntax +//@[027:033) | | | └─StringSyntax +//@[027:033) | | | └─Token(StringComplete) |'Evie'| +//@[033:034) | | ├─Token(Comma) |,| +//@[035:043) | | ├─ArrayItemSyntax +//@[035:043) | | | └─StringSyntax +//@[035:043) | | | └─Token(StringComplete) |'Casper'| +//@[043:044) | | └─Token(RightSquare) |]| +//@[044:045) | ├─Token(Comma) |,| +//@[046:068) | ├─FunctionArgumentSyntax +//@[046:068) | | └─LambdaSyntax +//@[046:050) | | ├─LocalVariableSyntax +//@[046:050) | | | └─IdentifierSyntax +//@[046:050) | | | └─Token(Identifier) |name| +//@[051:053) | | ├─Token(Arrow) |=>| +//@[054:068) | | └─FunctionCallSyntax +//@[054:062) | | ├─IdentifierSyntax +//@[054:062) | | | └─Token(Identifier) |sayHello| +//@[062:063) | | ├─Token(LeftParen) |(| +//@[063:067) | | ├─FunctionArgumentSyntax +//@[063:067) | | | └─VariableAccessSyntax +//@[063:067) | | | └─IdentifierSyntax +//@[063:067) | | | └─Token(Identifier) |name| +//@[067:068) | | └─Token(RightParen) |)| +//@[068:069) | └─Token(RightParen) |)| +//@[069:071) ├─Token(NewLine) |\n\n| + +func objReturnType(name string) object => { +//@[000:068) ├─FunctionDeclarationSyntax +//@[000:004) | ├─Token(Identifier) |func| +//@[005:018) | ├─IdentifierSyntax +//@[005:018) | | └─Token(Identifier) |objReturnType| +//@[018:068) | └─TypedLambdaSyntax +//@[018:031) | ├─TypedVariableBlockSyntax +//@[018:019) | | ├─Token(LeftParen) |(| +//@[019:030) | | ├─TypedLocalVariableSyntax +//@[019:023) | | | ├─IdentifierSyntax +//@[019:023) | | | | └─Token(Identifier) |name| +//@[024:030) | | | └─VariableAccessSyntax +//@[024:030) | | | └─IdentifierSyntax +//@[024:030) | | | └─Token(Identifier) |string| +//@[030:031) | | └─Token(RightParen) |)| +//@[032:038) | ├─VariableAccessSyntax +//@[032:038) | | └─IdentifierSyntax +//@[032:038) | | └─Token(Identifier) |object| +//@[039:041) | ├─Token(Arrow) |=>| +//@[042:068) | └─ObjectSyntax +//@[042:043) | ├─Token(LeftBrace) |{| +//@[043:044) | ├─Token(NewLine) |\n| + hello: 'Hi ${name}!' +//@[002:022) | ├─ObjectPropertySyntax +//@[002:007) | | ├─IdentifierSyntax +//@[002:007) | | | └─Token(Identifier) |hello| +//@[007:008) | | ├─Token(Colon) |:| +//@[009:022) | | └─StringSyntax +//@[009:015) | | ├─Token(StringLeftPiece) |'Hi ${| +//@[015:019) | | ├─VariableAccessSyntax +//@[015:019) | | | └─IdentifierSyntax +//@[015:019) | | | └─Token(Identifier) |name| +//@[019:022) | | └─Token(StringRightPiece) |}!'| +//@[022:023) | ├─Token(NewLine) |\n| +} +//@[000:001) | └─Token(RightBrace) |}| +//@[001:003) ├─Token(NewLine) |\n\n| + +func arrayReturnType(name string) array => [ +//@[000:053) ├─FunctionDeclarationSyntax +//@[000:004) | ├─Token(Identifier) |func| +//@[005:020) | ├─IdentifierSyntax +//@[005:020) | | └─Token(Identifier) |arrayReturnType| +//@[020:053) | └─TypedLambdaSyntax +//@[020:033) | ├─TypedVariableBlockSyntax +//@[020:021) | | ├─Token(LeftParen) |(| +//@[021:032) | | ├─TypedLocalVariableSyntax +//@[021:025) | | | ├─IdentifierSyntax +//@[021:025) | | | | └─Token(Identifier) |name| +//@[026:032) | | | └─VariableAccessSyntax +//@[026:032) | | | └─IdentifierSyntax +//@[026:032) | | | └─Token(Identifier) |string| +//@[032:033) | | └─Token(RightParen) |)| +//@[034:039) | ├─VariableAccessSyntax +//@[034:039) | | └─IdentifierSyntax +//@[034:039) | | └─Token(Identifier) |array| +//@[040:042) | ├─Token(Arrow) |=>| +//@[043:053) | └─ArraySyntax +//@[043:044) | ├─Token(LeftSquare) |[| +//@[044:045) | ├─Token(NewLine) |\n| + name +//@[002:006) | ├─ArrayItemSyntax +//@[002:006) | | └─VariableAccessSyntax +//@[002:006) | | └─IdentifierSyntax +//@[002:006) | | └─Token(Identifier) |name| +//@[006:007) | ├─Token(NewLine) |\n| +] +//@[000:001) | └─Token(RightSquare) |]| +//@[001:003) ├─Token(NewLine) |\n\n| + +func asdf(name string) array => [ +//@[000:051) ├─FunctionDeclarationSyntax +//@[000:004) | ├─Token(Identifier) |func| +//@[005:009) | ├─IdentifierSyntax +//@[005:009) | | └─Token(Identifier) |asdf| +//@[009:051) | └─TypedLambdaSyntax +//@[009:022) | ├─TypedVariableBlockSyntax +//@[009:010) | | ├─Token(LeftParen) |(| +//@[010:021) | | ├─TypedLocalVariableSyntax +//@[010:014) | | | ├─IdentifierSyntax +//@[010:014) | | | | └─Token(Identifier) |name| +//@[015:021) | | | └─VariableAccessSyntax +//@[015:021) | | | └─IdentifierSyntax +//@[015:021) | | | └─Token(Identifier) |string| +//@[021:022) | | └─Token(RightParen) |)| +//@[023:028) | ├─VariableAccessSyntax +//@[023:028) | | └─IdentifierSyntax +//@[023:028) | | └─Token(Identifier) |array| +//@[029:031) | ├─Token(Arrow) |=>| +//@[032:051) | └─ArraySyntax +//@[032:033) | ├─Token(LeftSquare) |[| +//@[033:034) | ├─Token(NewLine) |\n| + 'asdf' +//@[002:008) | ├─ArrayItemSyntax +//@[002:008) | | └─StringSyntax +//@[002:008) | | └─Token(StringComplete) |'asdf'| +//@[008:009) | ├─Token(NewLine) |\n| + name +//@[002:006) | ├─ArrayItemSyntax +//@[002:006) | | └─VariableAccessSyntax +//@[002:006) | | └─IdentifierSyntax +//@[002:006) | | └─Token(Identifier) |name| +//@[006:007) | ├─Token(NewLine) |\n| +] +//@[000:001) | └─Token(RightSquare) |]| +//@[001:002) ├─Token(NewLine) |\n| + +//@[000:000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/Functions_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/Functions_LF/main.tokens.bicep new file mode 100644 index 00000000000..77f79200af5 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/Functions_LF/main.tokens.bicep @@ -0,0 +1,150 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' +//@[000:004) Identifier |func| +//@[005:013) Identifier |buildUrl| +//@[013:014) LeftParen |(| +//@[014:019) Identifier |https| +//@[020:024) Identifier |bool| +//@[024:025) Comma |,| +//@[026:034) Identifier |hostname| +//@[035:041) Identifier |string| +//@[041:042) Comma |,| +//@[043:047) Identifier |path| +//@[048:054) Identifier |string| +//@[054:055) RightParen |)| +//@[056:062) Identifier |string| +//@[063:065) Arrow |=>| +//@[066:069) StringLeftPiece |'${| +//@[069:074) Identifier |https| +//@[075:076) Question |?| +//@[077:084) StringComplete |'https'| +//@[085:086) Colon |:| +//@[087:093) StringComplete |'http'| +//@[093:099) StringMiddlePiece |}://${| +//@[099:107) Identifier |hostname| +//@[107:110) StringMiddlePiece |}${| +//@[110:115) Identifier |empty| +//@[115:116) LeftParen |(| +//@[116:120) Identifier |path| +//@[120:121) RightParen |)| +//@[122:123) Question |?| +//@[124:126) StringComplete |''| +//@[127:128) Colon |:| +//@[129:133) StringLeftPiece |'/${| +//@[133:137) Identifier |path| +//@[137:139) StringRightPiece |}'| +//@[139:141) StringRightPiece |}'| +//@[141:143) NewLine |\n\n| + +output foo string = buildUrl(true, 'google.com', 'search') +//@[000:006) Identifier |output| +//@[007:010) Identifier |foo| +//@[011:017) Identifier |string| +//@[018:019) Assignment |=| +//@[020:028) Identifier |buildUrl| +//@[028:029) LeftParen |(| +//@[029:033) TrueKeyword |true| +//@[033:034) Comma |,| +//@[035:047) StringComplete |'google.com'| +//@[047:048) Comma |,| +//@[049:057) StringComplete |'search'| +//@[057:058) RightParen |)| +//@[058:060) NewLine |\n\n| + +func sayHello(name string) string => 'Hi ${name}!' +//@[000:004) Identifier |func| +//@[005:013) Identifier |sayHello| +//@[013:014) LeftParen |(| +//@[014:018) Identifier |name| +//@[019:025) Identifier |string| +//@[025:026) RightParen |)| +//@[027:033) Identifier |string| +//@[034:036) Arrow |=>| +//@[037:043) StringLeftPiece |'Hi ${| +//@[043:047) Identifier |name| +//@[047:050) StringRightPiece |}!'| +//@[050:052) NewLine |\n\n| + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) +//@[000:006) Identifier |output| +//@[007:013) Identifier |hellos| +//@[014:019) Identifier |array| +//@[020:021) Assignment |=| +//@[022:025) Identifier |map| +//@[025:026) LeftParen |(| +//@[026:027) LeftSquare |[| +//@[027:033) StringComplete |'Evie'| +//@[033:034) Comma |,| +//@[035:043) StringComplete |'Casper'| +//@[043:044) RightSquare |]| +//@[044:045) Comma |,| +//@[046:050) Identifier |name| +//@[051:053) Arrow |=>| +//@[054:062) Identifier |sayHello| +//@[062:063) LeftParen |(| +//@[063:067) Identifier |name| +//@[067:068) RightParen |)| +//@[068:069) RightParen |)| +//@[069:071) NewLine |\n\n| + +func objReturnType(name string) object => { +//@[000:004) Identifier |func| +//@[005:018) Identifier |objReturnType| +//@[018:019) LeftParen |(| +//@[019:023) Identifier |name| +//@[024:030) Identifier |string| +//@[030:031) RightParen |)| +//@[032:038) Identifier |object| +//@[039:041) Arrow |=>| +//@[042:043) LeftBrace |{| +//@[043:044) NewLine |\n| + hello: 'Hi ${name}!' +//@[002:007) Identifier |hello| +//@[007:008) Colon |:| +//@[009:015) StringLeftPiece |'Hi ${| +//@[015:019) Identifier |name| +//@[019:022) StringRightPiece |}!'| +//@[022:023) NewLine |\n| +} +//@[000:001) RightBrace |}| +//@[001:003) NewLine |\n\n| + +func arrayReturnType(name string) array => [ +//@[000:004) Identifier |func| +//@[005:020) Identifier |arrayReturnType| +//@[020:021) LeftParen |(| +//@[021:025) Identifier |name| +//@[026:032) Identifier |string| +//@[032:033) RightParen |)| +//@[034:039) Identifier |array| +//@[040:042) Arrow |=>| +//@[043:044) LeftSquare |[| +//@[044:045) NewLine |\n| + name +//@[002:006) Identifier |name| +//@[006:007) NewLine |\n| +] +//@[000:001) RightSquare |]| +//@[001:003) NewLine |\n\n| + +func asdf(name string) array => [ +//@[000:004) Identifier |func| +//@[005:009) Identifier |asdf| +//@[009:010) LeftParen |(| +//@[010:014) Identifier |name| +//@[015:021) Identifier |string| +//@[021:022) RightParen |)| +//@[023:028) Identifier |array| +//@[029:031) Arrow |=>| +//@[032:033) LeftSquare |[| +//@[033:034) NewLine |\n| + 'asdf' +//@[002:008) StringComplete |'asdf'| +//@[008:009) NewLine |\n| + name +//@[002:006) Identifier |name| +//@[006:007) NewLine |\n| +] +//@[000:001) RightSquare |]| +//@[001:002) NewLine |\n| + +//@[000:000) EndOfFile || diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/bicepconfig.json b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/bicepconfig.json new file mode 100644 index 00000000000..423f9d8f2d6 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/bicepconfig.json @@ -0,0 +1,6 @@ +{ + "experimentalFeaturesEnabled": { + "userDefinedTypes": true, + "userDefinedFunctions": true + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.bicep new file mode 100644 index 00000000000..bbfce2ff18f --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.bicep @@ -0,0 +1,35 @@ +func useRuntimeFunction() string => reference('foo').bar + +func constFunc() string => 'A' +func funcWithOtherFuncRef() string => constFunc() + +func missingArgType(input) string => input + +func missingOutputType(input string) => input + +func invalidType(input string) string => input + +output invalidType string = invalidType(true) + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' + +func noLambda('foo') string => '' + +func noLambda2 = (sdf 'foo') string => '' + +func noLambda3 = string 'asdf' + +func argLengthMismatch(a string, b string, c string) array => ([a, b, c]) +var sdf = argLengthMismatch('asdf') + +var asdfwdf = noLambda('asd') + +func sayHello(name string) string => 'Hi ${name}!' +output hellos array = map(['Evie', 'Casper'], sayHello) // this syntax not supported currently, but should it be? + +func sayHelloBadNewlines( + name string) string => 'Hi ${name}!' + +type validStringLiteralUnion = 'foo'|'bar'|'baz' +func invalidArgs(a validStringLiteralUnion, b string) string => a +func invalidOutput() validStringLiteralUnion => 'foo' diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.diagnostics.bicep new file mode 100644 index 00000000000..866c80a6ded --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.diagnostics.bicep @@ -0,0 +1,69 @@ +func useRuntimeFunction() string => reference('foo').bar +//@[36:52) [BCP341 (Error)] This expression is being used inside a function declaration, which requires a value that can be calculated at the start of the deployment. (CodeDescription: none) |reference('foo')| + +func constFunc() string => 'A' +func funcWithOtherFuncRef() string => constFunc() +//@[38:47) [BCP057 (Error)] The name "constFunc" does not exist in the current context. (CodeDescription: none) |constFunc| + +func missingArgType(input) string => input +//@[20:25) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) |input| +//@[25:26) [BCP279 (Error)] Expected a type at this location. Please specify a valid type expression or one of the following types: "array", "bool", "int", "object", "string". (CodeDescription: none) |)| + +func missingOutputType(input string) => input +//@[37:39) [BCP279 (Error)] Expected a type at this location. Please specify a valid type expression or one of the following types: "array", "bool", "int", "object", "string". (CodeDescription: none) |=>| +//@[37:45) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) |=> input| +//@[45:45) [BCP009 (Error)] Expected a literal value, an array, an object, a parenthesized expression, or a function call at this location. (CodeDescription: none) || +//@[45:45) [BCP018 (Error)] Expected the "=>" character at this location. (CodeDescription: none) || + +func invalidType(input string) string => input + +output invalidType string = invalidType(true) +//@[40:44) [BCP070 (Error)] Argument of type "true" is not assignable to parameter of type "string". (CodeDescription: none) |true| + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' +//@[22:30) [BCP302 (Error)] The name "notAType" is not a valid type. Please specify one of the following types: "array", "bool", "int", "object", "string", "validStringLiteralUnion". (CodeDescription: none) |notAType| +//@[34:46) [BCP302 (Error)] The name "alsoNotAType" is not a valid type. Please specify one of the following types: "array", "bool", "int", "object", "string", "validStringLiteralUnion". (CodeDescription: none) |alsoNotAType| +//@[61:62) [BCP062 (Error)] The referenced declaration with name "a" is not valid. (CodeDescription: none) |a| +//@[66:67) [BCP062 (Error)] The referenced declaration with name "b" is not valid. (CodeDescription: none) |b| + +func noLambda('foo') string => '' +//@[14:14) [BCP015 (Error)] Expected a variable identifier at this location. (CodeDescription: none) || +//@[14:19) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) |'foo'| + +func noLambda2 = (sdf 'foo') string => '' +//@[15:16) [BCP018 (Error)] Expected the "(" character at this location. (CodeDescription: none) |=| + +func noLambda3 = string 'asdf' +//@[15:16) [BCP018 (Error)] Expected the "(" character at this location. (CodeDescription: none) |=| + +func argLengthMismatch(a string, b string, c string) array => ([a, b, c]) +var sdf = argLengthMismatch('asdf') +//@[04:07) [no-unused-vars (Warning)] Variable "sdf" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |sdf| +//@[27:35) [BCP071 (Error)] Expected 3 arguments, but got 1. (CodeDescription: none) |('asdf')| + +var asdfwdf = noLambda('asd') +//@[04:11) [no-unused-vars (Warning)] Variable "asdfwdf" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |asdfwdf| +//@[23:28) [BCP070 (Error)] Argument of type "'asd'" is not assignable to parameter of type "'foo'". (CodeDescription: none) |'asd'| + +func sayHello(name string) string => 'Hi ${name}!' +output hellos array = map(['Evie', 'Casper'], sayHello) // this syntax not supported currently, but should it be? +//@[46:54) [BCP063 (Error)] The name "sayHello" is not a parameter, variable, resource or module. (CodeDescription: none) |sayHello| + +func sayHelloBadNewlines( +//@[25:25) [BCP009 (Error)] Expected a literal value, an array, an object, a parenthesized expression, or a function call at this location. (CodeDescription: none) || +//@[25:25) [BCP279 (Error)] Expected a type at this location. Please specify a valid type expression or one of the following types: "array", "bool", "int", "object", "string". (CodeDescription: none) || +//@[25:25) [BCP279 (Error)] Expected a type at this location. Please specify a valid type expression or one of the following types: "array", "bool", "int", "object", "string". (CodeDescription: none) || +//@[25:25) [BCP015 (Error)] Expected a variable identifier at this location. (CodeDescription: none) || +//@[25:25) [BCP018 (Error)] Expected the ")" character at this location. (CodeDescription: none) || +//@[25:25) [BCP018 (Error)] Expected the "=>" character at this location. (CodeDescription: none) || +//@[25:25) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) || +//@[25:25) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) || + name string) string => 'Hi ${name}!' +//@[02:06) [BCP007 (Error)] This declaration type is not recognized. Specify a metadata, parameter, variable, resource, or output declaration. (CodeDescription: none) |name| + +type validStringLiteralUnion = 'foo'|'bar'|'baz' +func invalidArgs(a validStringLiteralUnion, b string) string => a +//@[17:42) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) |a validStringLiteralUnion| +func invalidOutput() validStringLiteralUnion => 'foo' +//@[21:44) [BCP342 (Error)] User-defined types are not supported in user-defined function parameters or outputs. (CodeDescription: none) |validStringLiteralUnion| + diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.formatted.bicep new file mode 100644 index 00000000000..e880bf1c00c --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.formatted.bicep @@ -0,0 +1,35 @@ +func useRuntimeFunction() string => reference('foo').bar + +func constFunc() string => 'A' +func funcWithOtherFuncRef() string => constFunc() + +func missingArgType(input) string => input + +func missingOutputType(input string) => input + +func invalidType(input string) string => input + +output invalidType string = invalidType(true) + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' + +func noLambda('foo') string => '' + +func noLambda2 = (sdf 'foo') string => '' + +func noLambda3 = string 'asdf' + +func argLengthMismatch(a string, b string, c string) array => ([ a, b, c ]) +var sdf = argLengthMismatch('asdf') + +var asdfwdf = noLambda('asd') + +func sayHello(name string) string => 'Hi ${name}!' +output hellos array = map([ 'Evie', 'Casper' ], sayHello) // this syntax not supported currently, but should it be? + +func sayHelloBadNewlines( +name string) string => 'Hi ${name}!' + +type validStringLiteralUnion = 'foo' | 'bar' | 'baz' +func invalidArgs(a validStringLiteralUnion, b string) string => a +func invalidOutput() validStringLiteralUnion => 'foo' diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.symbols.bicep new file mode 100644 index 00000000000..866ed538d01 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.symbols.bicep @@ -0,0 +1,69 @@ +func useRuntimeFunction() string => reference('foo').bar +//@[05:23) Function useRuntimeFunction. Type: () => any. Declaration start char: 0, length: 56 + +func constFunc() string => 'A' +//@[05:14) Function constFunc. Type: () => 'A'. Declaration start char: 0, length: 30 +func funcWithOtherFuncRef() string => constFunc() +//@[05:25) Function funcWithOtherFuncRef. Type: () => string. Declaration start char: 0, length: 49 + +func missingArgType(input) string => input +//@[20:25) Local input. Type: any. Declaration start char: 20, length: 5 +//@[05:19) Function missingArgType. Type: any => any. Declaration start char: 0, length: 42 + +func missingOutputType(input string) => input +//@[23:28) Local input. Type: string. Declaration start char: 23, length: 12 +//@[05:22) Function missingOutputType. Type: string => any. Declaration start char: 0, length: 45 + +func invalidType(input string) string => input +//@[17:22) Local input. Type: string. Declaration start char: 17, length: 12 +//@[05:16) Function invalidType. Type: string => string. Declaration start char: 0, length: 46 + +output invalidType string = invalidType(true) +//@[07:18) Output invalidType. Type: string. Declaration start char: 0, length: 45 + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' +//@[20:21) Local a. Type: error. Declaration start char: 20, length: 10 +//@[32:33) Local b. Type: error. Declaration start char: 32, length: 14 +//@[05:19) Function madeUpTypeArgs. Type: error. Declaration start char: 0, length: 69 + +func noLambda('foo') string => '' +//@[14:14) Local . Type: 'foo'. Declaration start char: 14, length: 5 +//@[05:13) Function noLambda. Type: 'foo' => ''. Declaration start char: 0, length: 33 + +func noLambda2 = (sdf 'foo') string => '' +//@[05:14) Function noLambda2. Type: error. Declaration start char: 0, length: 41 + +func noLambda3 = string 'asdf' +//@[05:14) Function noLambda3. Type: error. Declaration start char: 0, length: 30 + +func argLengthMismatch(a string, b string, c string) array => ([a, b, c]) +//@[23:24) Local a. Type: string. Declaration start char: 23, length: 8 +//@[33:34) Local b. Type: string. Declaration start char: 33, length: 8 +//@[43:44) Local c. Type: string. Declaration start char: 43, length: 8 +//@[05:22) Function argLengthMismatch. Type: (string, string, string) => string[]. Declaration start char: 0, length: 73 +var sdf = argLengthMismatch('asdf') +//@[04:07) Variable sdf. Type: error. Declaration start char: 0, length: 35 + +var asdfwdf = noLambda('asd') +//@[04:11) Variable asdfwdf. Type: error. Declaration start char: 0, length: 29 + +func sayHello(name string) string => 'Hi ${name}!' +//@[14:18) Local name. Type: string. Declaration start char: 14, length: 11 +//@[05:13) Function sayHello. Type: string => string. Declaration start char: 0, length: 50 +output hellos array = map(['Evie', 'Casper'], sayHello) // this syntax not supported currently, but should it be? +//@[07:13) Output hellos. Type: array. Declaration start char: 0, length: 55 + +func sayHelloBadNewlines( +//@[25:25) Local . Type: any. Declaration start char: 25, length: 0 +//@[05:24) Function sayHelloBadNewlines. Type: any => any. Declaration start char: 0, length: 25 + name string) string => 'Hi ${name}!' + +type validStringLiteralUnion = 'foo'|'bar'|'baz' +//@[05:28) TypeAlias validStringLiteralUnion. Type: Type<'bar' | 'baz' | 'foo'>. Declaration start char: 0, length: 48 +func invalidArgs(a validStringLiteralUnion, b string) string => a +//@[17:18) Local a. Type: 'bar' | 'baz' | 'foo'. Declaration start char: 17, length: 25 +//@[44:45) Local b. Type: string. Declaration start char: 44, length: 8 +//@[05:16) Function invalidArgs. Type: (('bar' | 'baz' | 'foo'), string) => ('bar' | 'baz' | 'foo'). Declaration start char: 0, length: 65 +func invalidOutput() validStringLiteralUnion => 'foo' +//@[05:18) Function invalidOutput. Type: () => 'foo'. Declaration start char: 0, length: 53 + diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.syntax.bicep new file mode 100644 index 00000000000..090f36eb72a --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.syntax.bicep @@ -0,0 +1,478 @@ +func useRuntimeFunction() string => reference('foo').bar +//@[000:1050) ProgramSyntax +//@[000:0056) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0023) | ├─IdentifierSyntax +//@[005:0023) | | └─Token(Identifier) |useRuntimeFunction| +//@[023:0056) | └─TypedLambdaSyntax +//@[023:0025) | ├─TypedVariableBlockSyntax +//@[023:0024) | | ├─Token(LeftParen) |(| +//@[024:0025) | | └─Token(RightParen) |)| +//@[026:0032) | ├─VariableAccessSyntax +//@[026:0032) | | └─IdentifierSyntax +//@[026:0032) | | └─Token(Identifier) |string| +//@[033:0035) | ├─Token(Arrow) |=>| +//@[036:0056) | └─PropertyAccessSyntax +//@[036:0052) | ├─FunctionCallSyntax +//@[036:0045) | | ├─IdentifierSyntax +//@[036:0045) | | | └─Token(Identifier) |reference| +//@[045:0046) | | ├─Token(LeftParen) |(| +//@[046:0051) | | ├─FunctionArgumentSyntax +//@[046:0051) | | | └─StringSyntax +//@[046:0051) | | | └─Token(StringComplete) |'foo'| +//@[051:0052) | | └─Token(RightParen) |)| +//@[052:0053) | ├─Token(Dot) |.| +//@[053:0056) | └─IdentifierSyntax +//@[053:0056) | └─Token(Identifier) |bar| +//@[056:0058) ├─Token(NewLine) |\n\n| + +func constFunc() string => 'A' +//@[000:0030) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0014) | ├─IdentifierSyntax +//@[005:0014) | | └─Token(Identifier) |constFunc| +//@[014:0030) | └─TypedLambdaSyntax +//@[014:0016) | ├─TypedVariableBlockSyntax +//@[014:0015) | | ├─Token(LeftParen) |(| +//@[015:0016) | | └─Token(RightParen) |)| +//@[017:0023) | ├─VariableAccessSyntax +//@[017:0023) | | └─IdentifierSyntax +//@[017:0023) | | └─Token(Identifier) |string| +//@[024:0026) | ├─Token(Arrow) |=>| +//@[027:0030) | └─StringSyntax +//@[027:0030) | └─Token(StringComplete) |'A'| +//@[030:0031) ├─Token(NewLine) |\n| +func funcWithOtherFuncRef() string => constFunc() +//@[000:0049) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0025) | ├─IdentifierSyntax +//@[005:0025) | | └─Token(Identifier) |funcWithOtherFuncRef| +//@[025:0049) | └─TypedLambdaSyntax +//@[025:0027) | ├─TypedVariableBlockSyntax +//@[025:0026) | | ├─Token(LeftParen) |(| +//@[026:0027) | | └─Token(RightParen) |)| +//@[028:0034) | ├─VariableAccessSyntax +//@[028:0034) | | └─IdentifierSyntax +//@[028:0034) | | └─Token(Identifier) |string| +//@[035:0037) | ├─Token(Arrow) |=>| +//@[038:0049) | └─FunctionCallSyntax +//@[038:0047) | ├─IdentifierSyntax +//@[038:0047) | | └─Token(Identifier) |constFunc| +//@[047:0048) | ├─Token(LeftParen) |(| +//@[048:0049) | └─Token(RightParen) |)| +//@[049:0051) ├─Token(NewLine) |\n\n| + +func missingArgType(input) string => input +//@[000:0042) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0019) | ├─IdentifierSyntax +//@[005:0019) | | └─Token(Identifier) |missingArgType| +//@[019:0042) | └─TypedLambdaSyntax +//@[019:0026) | ├─TypedVariableBlockSyntax +//@[019:0020) | | ├─Token(LeftParen) |(| +//@[020:0025) | | ├─TypedLocalVariableSyntax +//@[020:0025) | | | ├─IdentifierSyntax +//@[020:0025) | | | | └─Token(Identifier) |input| +//@[025:0025) | | | └─SkippedTriviaSyntax +//@[025:0026) | | └─Token(RightParen) |)| +//@[027:0033) | ├─VariableAccessSyntax +//@[027:0033) | | └─IdentifierSyntax +//@[027:0033) | | └─Token(Identifier) |string| +//@[034:0036) | ├─Token(Arrow) |=>| +//@[037:0042) | └─VariableAccessSyntax +//@[037:0042) | └─IdentifierSyntax +//@[037:0042) | └─Token(Identifier) |input| +//@[042:0044) ├─Token(NewLine) |\n\n| + +func missingOutputType(input string) => input +//@[000:0045) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0022) | ├─IdentifierSyntax +//@[005:0022) | | └─Token(Identifier) |missingOutputType| +//@[022:0045) | └─TypedLambdaSyntax +//@[022:0036) | ├─TypedVariableBlockSyntax +//@[022:0023) | | ├─Token(LeftParen) |(| +//@[023:0035) | | ├─TypedLocalVariableSyntax +//@[023:0028) | | | ├─IdentifierSyntax +//@[023:0028) | | | | └─Token(Identifier) |input| +//@[029:0035) | | | └─VariableAccessSyntax +//@[029:0035) | | | └─IdentifierSyntax +//@[029:0035) | | | └─Token(Identifier) |string| +//@[035:0036) | | └─Token(RightParen) |)| +//@[037:0045) | ├─SkippedTriviaSyntax +//@[037:0039) | | ├─Token(Arrow) |=>| +//@[040:0045) | | └─Token(Identifier) |input| +//@[045:0045) | ├─SkippedTriviaSyntax +//@[045:0045) | └─SkippedTriviaSyntax +//@[045:0047) ├─Token(NewLine) |\n\n| + +func invalidType(input string) string => input +//@[000:0046) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0016) | ├─IdentifierSyntax +//@[005:0016) | | └─Token(Identifier) |invalidType| +//@[016:0046) | └─TypedLambdaSyntax +//@[016:0030) | ├─TypedVariableBlockSyntax +//@[016:0017) | | ├─Token(LeftParen) |(| +//@[017:0029) | | ├─TypedLocalVariableSyntax +//@[017:0022) | | | ├─IdentifierSyntax +//@[017:0022) | | | | └─Token(Identifier) |input| +//@[023:0029) | | | └─VariableAccessSyntax +//@[023:0029) | | | └─IdentifierSyntax +//@[023:0029) | | | └─Token(Identifier) |string| +//@[029:0030) | | └─Token(RightParen) |)| +//@[031:0037) | ├─VariableAccessSyntax +//@[031:0037) | | └─IdentifierSyntax +//@[031:0037) | | └─Token(Identifier) |string| +//@[038:0040) | ├─Token(Arrow) |=>| +//@[041:0046) | └─VariableAccessSyntax +//@[041:0046) | └─IdentifierSyntax +//@[041:0046) | └─Token(Identifier) |input| +//@[046:0048) ├─Token(NewLine) |\n\n| + +output invalidType string = invalidType(true) +//@[000:0045) ├─OutputDeclarationSyntax +//@[000:0006) | ├─Token(Identifier) |output| +//@[007:0018) | ├─IdentifierSyntax +//@[007:0018) | | └─Token(Identifier) |invalidType| +//@[019:0025) | ├─VariableAccessSyntax +//@[019:0025) | | └─IdentifierSyntax +//@[019:0025) | | └─Token(Identifier) |string| +//@[026:0027) | ├─Token(Assignment) |=| +//@[028:0045) | └─FunctionCallSyntax +//@[028:0039) | ├─IdentifierSyntax +//@[028:0039) | | └─Token(Identifier) |invalidType| +//@[039:0040) | ├─Token(LeftParen) |(| +//@[040:0044) | ├─FunctionArgumentSyntax +//@[040:0044) | | └─BooleanLiteralSyntax +//@[040:0044) | | └─Token(TrueKeyword) |true| +//@[044:0045) | └─Token(RightParen) |)| +//@[045:0047) ├─Token(NewLine) |\n\n| + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' +//@[000:0069) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0019) | ├─IdentifierSyntax +//@[005:0019) | | └─Token(Identifier) |madeUpTypeArgs| +//@[019:0069) | └─TypedLambdaSyntax +//@[019:0047) | ├─TypedVariableBlockSyntax +//@[019:0020) | | ├─Token(LeftParen) |(| +//@[020:0030) | | ├─TypedLocalVariableSyntax +//@[020:0021) | | | ├─IdentifierSyntax +//@[020:0021) | | | | └─Token(Identifier) |a| +//@[022:0030) | | | └─VariableAccessSyntax +//@[022:0030) | | | └─IdentifierSyntax +//@[022:0030) | | | └─Token(Identifier) |notAType| +//@[030:0031) | | ├─Token(Comma) |,| +//@[032:0046) | | ├─TypedLocalVariableSyntax +//@[032:0033) | | | ├─IdentifierSyntax +//@[032:0033) | | | | └─Token(Identifier) |b| +//@[034:0046) | | | └─VariableAccessSyntax +//@[034:0046) | | | └─IdentifierSyntax +//@[034:0046) | | | └─Token(Identifier) |alsoNotAType| +//@[046:0047) | | └─Token(RightParen) |)| +//@[048:0054) | ├─VariableAccessSyntax +//@[048:0054) | | └─IdentifierSyntax +//@[048:0054) | | └─Token(Identifier) |string| +//@[055:0057) | ├─Token(Arrow) |=>| +//@[058:0069) | └─StringSyntax +//@[058:0061) | ├─Token(StringLeftPiece) |'${| +//@[061:0062) | ├─VariableAccessSyntax +//@[061:0062) | | └─IdentifierSyntax +//@[061:0062) | | └─Token(Identifier) |a| +//@[062:0066) | ├─Token(StringMiddlePiece) |}-${| +//@[066:0067) | ├─VariableAccessSyntax +//@[066:0067) | | └─IdentifierSyntax +//@[066:0067) | | └─Token(Identifier) |b| +//@[067:0069) | └─Token(StringRightPiece) |}'| +//@[069:0071) ├─Token(NewLine) |\n\n| + +func noLambda('foo') string => '' +//@[000:0033) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0013) | ├─IdentifierSyntax +//@[005:0013) | | └─Token(Identifier) |noLambda| +//@[013:0033) | └─TypedLambdaSyntax +//@[013:0020) | ├─TypedVariableBlockSyntax +//@[013:0014) | | ├─Token(LeftParen) |(| +//@[014:0019) | | ├─TypedLocalVariableSyntax +//@[014:0014) | | | ├─IdentifierSyntax +//@[014:0014) | | | | └─SkippedTriviaSyntax +//@[014:0019) | | | └─StringSyntax +//@[014:0019) | | | └─Token(StringComplete) |'foo'| +//@[019:0020) | | └─Token(RightParen) |)| +//@[021:0027) | ├─VariableAccessSyntax +//@[021:0027) | | └─IdentifierSyntax +//@[021:0027) | | └─Token(Identifier) |string| +//@[028:0030) | ├─Token(Arrow) |=>| +//@[031:0033) | └─StringSyntax +//@[031:0033) | └─Token(StringComplete) |''| +//@[033:0035) ├─Token(NewLine) |\n\n| + +func noLambda2 = (sdf 'foo') string => '' +//@[000:0041) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0014) | ├─IdentifierSyntax +//@[005:0014) | | └─Token(Identifier) |noLambda2| +//@[015:0041) | └─SkippedTriviaSyntax +//@[015:0016) | ├─Token(Assignment) |=| +//@[017:0018) | ├─Token(LeftParen) |(| +//@[018:0021) | ├─Token(Identifier) |sdf| +//@[022:0027) | ├─Token(StringComplete) |'foo'| +//@[027:0028) | ├─Token(RightParen) |)| +//@[029:0035) | ├─Token(Identifier) |string| +//@[036:0038) | ├─Token(Arrow) |=>| +//@[039:0041) | └─Token(StringComplete) |''| +//@[041:0043) ├─Token(NewLine) |\n\n| + +func noLambda3 = string 'asdf' +//@[000:0030) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0014) | ├─IdentifierSyntax +//@[005:0014) | | └─Token(Identifier) |noLambda3| +//@[015:0030) | └─SkippedTriviaSyntax +//@[015:0016) | ├─Token(Assignment) |=| +//@[017:0023) | ├─Token(Identifier) |string| +//@[024:0030) | └─Token(StringComplete) |'asdf'| +//@[030:0032) ├─Token(NewLine) |\n\n| + +func argLengthMismatch(a string, b string, c string) array => ([a, b, c]) +//@[000:0073) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0022) | ├─IdentifierSyntax +//@[005:0022) | | └─Token(Identifier) |argLengthMismatch| +//@[022:0073) | └─TypedLambdaSyntax +//@[022:0052) | ├─TypedVariableBlockSyntax +//@[022:0023) | | ├─Token(LeftParen) |(| +//@[023:0031) | | ├─TypedLocalVariableSyntax +//@[023:0024) | | | ├─IdentifierSyntax +//@[023:0024) | | | | └─Token(Identifier) |a| +//@[025:0031) | | | └─VariableAccessSyntax +//@[025:0031) | | | └─IdentifierSyntax +//@[025:0031) | | | └─Token(Identifier) |string| +//@[031:0032) | | ├─Token(Comma) |,| +//@[033:0041) | | ├─TypedLocalVariableSyntax +//@[033:0034) | | | ├─IdentifierSyntax +//@[033:0034) | | | | └─Token(Identifier) |b| +//@[035:0041) | | | └─VariableAccessSyntax +//@[035:0041) | | | └─IdentifierSyntax +//@[035:0041) | | | └─Token(Identifier) |string| +//@[041:0042) | | ├─Token(Comma) |,| +//@[043:0051) | | ├─TypedLocalVariableSyntax +//@[043:0044) | | | ├─IdentifierSyntax +//@[043:0044) | | | | └─Token(Identifier) |c| +//@[045:0051) | | | └─VariableAccessSyntax +//@[045:0051) | | | └─IdentifierSyntax +//@[045:0051) | | | └─Token(Identifier) |string| +//@[051:0052) | | └─Token(RightParen) |)| +//@[053:0058) | ├─VariableAccessSyntax +//@[053:0058) | | └─IdentifierSyntax +//@[053:0058) | | └─Token(Identifier) |array| +//@[059:0061) | ├─Token(Arrow) |=>| +//@[062:0073) | └─ParenthesizedExpressionSyntax +//@[062:0063) | ├─Token(LeftParen) |(| +//@[063:0072) | ├─ArraySyntax +//@[063:0064) | | ├─Token(LeftSquare) |[| +//@[064:0065) | | ├─ArrayItemSyntax +//@[064:0065) | | | └─VariableAccessSyntax +//@[064:0065) | | | └─IdentifierSyntax +//@[064:0065) | | | └─Token(Identifier) |a| +//@[065:0066) | | ├─Token(Comma) |,| +//@[067:0068) | | ├─ArrayItemSyntax +//@[067:0068) | | | └─VariableAccessSyntax +//@[067:0068) | | | └─IdentifierSyntax +//@[067:0068) | | | └─Token(Identifier) |b| +//@[068:0069) | | ├─Token(Comma) |,| +//@[070:0071) | | ├─ArrayItemSyntax +//@[070:0071) | | | └─VariableAccessSyntax +//@[070:0071) | | | └─IdentifierSyntax +//@[070:0071) | | | └─Token(Identifier) |c| +//@[071:0072) | | └─Token(RightSquare) |]| +//@[072:0073) | └─Token(RightParen) |)| +//@[073:0074) ├─Token(NewLine) |\n| +var sdf = argLengthMismatch('asdf') +//@[000:0035) ├─VariableDeclarationSyntax +//@[000:0003) | ├─Token(Identifier) |var| +//@[004:0007) | ├─IdentifierSyntax +//@[004:0007) | | └─Token(Identifier) |sdf| +//@[008:0009) | ├─Token(Assignment) |=| +//@[010:0035) | └─FunctionCallSyntax +//@[010:0027) | ├─IdentifierSyntax +//@[010:0027) | | └─Token(Identifier) |argLengthMismatch| +//@[027:0028) | ├─Token(LeftParen) |(| +//@[028:0034) | ├─FunctionArgumentSyntax +//@[028:0034) | | └─StringSyntax +//@[028:0034) | | └─Token(StringComplete) |'asdf'| +//@[034:0035) | └─Token(RightParen) |)| +//@[035:0037) ├─Token(NewLine) |\n\n| + +var asdfwdf = noLambda('asd') +//@[000:0029) ├─VariableDeclarationSyntax +//@[000:0003) | ├─Token(Identifier) |var| +//@[004:0011) | ├─IdentifierSyntax +//@[004:0011) | | └─Token(Identifier) |asdfwdf| +//@[012:0013) | ├─Token(Assignment) |=| +//@[014:0029) | └─FunctionCallSyntax +//@[014:0022) | ├─IdentifierSyntax +//@[014:0022) | | └─Token(Identifier) |noLambda| +//@[022:0023) | ├─Token(LeftParen) |(| +//@[023:0028) | ├─FunctionArgumentSyntax +//@[023:0028) | | └─StringSyntax +//@[023:0028) | | └─Token(StringComplete) |'asd'| +//@[028:0029) | └─Token(RightParen) |)| +//@[029:0031) ├─Token(NewLine) |\n\n| + +func sayHello(name string) string => 'Hi ${name}!' +//@[000:0050) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0013) | ├─IdentifierSyntax +//@[005:0013) | | └─Token(Identifier) |sayHello| +//@[013:0050) | └─TypedLambdaSyntax +//@[013:0026) | ├─TypedVariableBlockSyntax +//@[013:0014) | | ├─Token(LeftParen) |(| +//@[014:0025) | | ├─TypedLocalVariableSyntax +//@[014:0018) | | | ├─IdentifierSyntax +//@[014:0018) | | | | └─Token(Identifier) |name| +//@[019:0025) | | | └─VariableAccessSyntax +//@[019:0025) | | | └─IdentifierSyntax +//@[019:0025) | | | └─Token(Identifier) |string| +//@[025:0026) | | └─Token(RightParen) |)| +//@[027:0033) | ├─VariableAccessSyntax +//@[027:0033) | | └─IdentifierSyntax +//@[027:0033) | | └─Token(Identifier) |string| +//@[034:0036) | ├─Token(Arrow) |=>| +//@[037:0050) | └─StringSyntax +//@[037:0043) | ├─Token(StringLeftPiece) |'Hi ${| +//@[043:0047) | ├─VariableAccessSyntax +//@[043:0047) | | └─IdentifierSyntax +//@[043:0047) | | └─Token(Identifier) |name| +//@[047:0050) | └─Token(StringRightPiece) |}!'| +//@[050:0051) ├─Token(NewLine) |\n| +output hellos array = map(['Evie', 'Casper'], sayHello) // this syntax not supported currently, but should it be? +//@[000:0055) ├─OutputDeclarationSyntax +//@[000:0006) | ├─Token(Identifier) |output| +//@[007:0013) | ├─IdentifierSyntax +//@[007:0013) | | └─Token(Identifier) |hellos| +//@[014:0019) | ├─VariableAccessSyntax +//@[014:0019) | | └─IdentifierSyntax +//@[014:0019) | | └─Token(Identifier) |array| +//@[020:0021) | ├─Token(Assignment) |=| +//@[022:0055) | └─FunctionCallSyntax +//@[022:0025) | ├─IdentifierSyntax +//@[022:0025) | | └─Token(Identifier) |map| +//@[025:0026) | ├─Token(LeftParen) |(| +//@[026:0044) | ├─FunctionArgumentSyntax +//@[026:0044) | | └─ArraySyntax +//@[026:0027) | | ├─Token(LeftSquare) |[| +//@[027:0033) | | ├─ArrayItemSyntax +//@[027:0033) | | | └─StringSyntax +//@[027:0033) | | | └─Token(StringComplete) |'Evie'| +//@[033:0034) | | ├─Token(Comma) |,| +//@[035:0043) | | ├─ArrayItemSyntax +//@[035:0043) | | | └─StringSyntax +//@[035:0043) | | | └─Token(StringComplete) |'Casper'| +//@[043:0044) | | └─Token(RightSquare) |]| +//@[044:0045) | ├─Token(Comma) |,| +//@[046:0054) | ├─FunctionArgumentSyntax +//@[046:0054) | | └─VariableAccessSyntax +//@[046:0054) | | └─IdentifierSyntax +//@[046:0054) | | └─Token(Identifier) |sayHello| +//@[054:0055) | └─Token(RightParen) |)| +//@[113:0115) ├─Token(NewLine) |\n\n| + +func sayHelloBadNewlines( +//@[000:0025) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0024) | ├─IdentifierSyntax +//@[005:0024) | | └─Token(Identifier) |sayHelloBadNewlines| +//@[024:0025) | └─TypedLambdaSyntax +//@[024:0025) | ├─TypedVariableBlockSyntax +//@[024:0025) | | ├─Token(LeftParen) |(| +//@[025:0025) | | ├─TypedLocalVariableSyntax +//@[025:0025) | | | ├─IdentifierSyntax +//@[025:0025) | | | | └─SkippedTriviaSyntax +//@[025:0025) | | | └─SkippedTriviaSyntax +//@[025:0025) | | └─SkippedTriviaSyntax +//@[025:0025) | ├─SkippedTriviaSyntax +//@[025:0025) | ├─SkippedTriviaSyntax +//@[025:0025) | └─SkippedTriviaSyntax +//@[025:0026) ├─Token(NewLine) |\n| + name string) string => 'Hi ${name}!' +//@[002:0038) ├─SkippedTriviaSyntax +//@[002:0006) | ├─Token(Identifier) |name| +//@[007:0013) | ├─Token(Identifier) |string| +//@[013:0014) | ├─Token(RightParen) |)| +//@[015:0021) | ├─Token(Identifier) |string| +//@[022:0024) | ├─Token(Arrow) |=>| +//@[025:0031) | ├─Token(StringLeftPiece) |'Hi ${| +//@[031:0035) | ├─Token(Identifier) |name| +//@[035:0038) | └─Token(StringRightPiece) |}!'| +//@[038:0040) ├─Token(NewLine) |\n\n| + +type validStringLiteralUnion = 'foo'|'bar'|'baz' +//@[000:0048) ├─TypeDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |type| +//@[005:0028) | ├─IdentifierSyntax +//@[005:0028) | | └─Token(Identifier) |validStringLiteralUnion| +//@[029:0030) | ├─Token(Assignment) |=| +//@[031:0048) | └─UnionTypeSyntax +//@[031:0036) | ├─UnionTypeMemberSyntax +//@[031:0036) | | └─StringSyntax +//@[031:0036) | | └─Token(StringComplete) |'foo'| +//@[036:0037) | ├─Token(Pipe) ||| +//@[037:0042) | ├─UnionTypeMemberSyntax +//@[037:0042) | | └─StringSyntax +//@[037:0042) | | └─Token(StringComplete) |'bar'| +//@[042:0043) | ├─Token(Pipe) ||| +//@[043:0048) | └─UnionTypeMemberSyntax +//@[043:0048) | └─StringSyntax +//@[043:0048) | └─Token(StringComplete) |'baz'| +//@[048:0049) ├─Token(NewLine) |\n| +func invalidArgs(a validStringLiteralUnion, b string) string => a +//@[000:0065) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0016) | ├─IdentifierSyntax +//@[005:0016) | | └─Token(Identifier) |invalidArgs| +//@[016:0065) | └─TypedLambdaSyntax +//@[016:0053) | ├─TypedVariableBlockSyntax +//@[016:0017) | | ├─Token(LeftParen) |(| +//@[017:0042) | | ├─TypedLocalVariableSyntax +//@[017:0018) | | | ├─IdentifierSyntax +//@[017:0018) | | | | └─Token(Identifier) |a| +//@[019:0042) | | | └─VariableAccessSyntax +//@[019:0042) | | | └─IdentifierSyntax +//@[019:0042) | | | └─Token(Identifier) |validStringLiteralUnion| +//@[042:0043) | | ├─Token(Comma) |,| +//@[044:0052) | | ├─TypedLocalVariableSyntax +//@[044:0045) | | | ├─IdentifierSyntax +//@[044:0045) | | | | └─Token(Identifier) |b| +//@[046:0052) | | | └─VariableAccessSyntax +//@[046:0052) | | | └─IdentifierSyntax +//@[046:0052) | | | └─Token(Identifier) |string| +//@[052:0053) | | └─Token(RightParen) |)| +//@[054:0060) | ├─VariableAccessSyntax +//@[054:0060) | | └─IdentifierSyntax +//@[054:0060) | | └─Token(Identifier) |string| +//@[061:0063) | ├─Token(Arrow) |=>| +//@[064:0065) | └─VariableAccessSyntax +//@[064:0065) | └─IdentifierSyntax +//@[064:0065) | └─Token(Identifier) |a| +//@[065:0066) ├─Token(NewLine) |\n| +func invalidOutput() validStringLiteralUnion => 'foo' +//@[000:0053) ├─FunctionDeclarationSyntax +//@[000:0004) | ├─Token(Identifier) |func| +//@[005:0018) | ├─IdentifierSyntax +//@[005:0018) | | └─Token(Identifier) |invalidOutput| +//@[018:0053) | └─TypedLambdaSyntax +//@[018:0020) | ├─TypedVariableBlockSyntax +//@[018:0019) | | ├─Token(LeftParen) |(| +//@[019:0020) | | └─Token(RightParen) |)| +//@[021:0044) | ├─VariableAccessSyntax +//@[021:0044) | | └─IdentifierSyntax +//@[021:0044) | | └─Token(Identifier) |validStringLiteralUnion| +//@[045:0047) | ├─Token(Arrow) |=>| +//@[048:0053) | └─StringSyntax +//@[048:0053) | └─Token(StringComplete) |'foo'| +//@[053:0054) ├─Token(NewLine) |\n| + +//@[000:0000) └─Token(EndOfFile) || diff --git a/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.tokens.bicep new file mode 100644 index 00000000000..0f1d87df910 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/InvalidFunctions_LF/main.tokens.bicep @@ -0,0 +1,258 @@ +func useRuntimeFunction() string => reference('foo').bar +//@[000:004) Identifier |func| +//@[005:023) Identifier |useRuntimeFunction| +//@[023:024) LeftParen |(| +//@[024:025) RightParen |)| +//@[026:032) Identifier |string| +//@[033:035) Arrow |=>| +//@[036:045) Identifier |reference| +//@[045:046) LeftParen |(| +//@[046:051) StringComplete |'foo'| +//@[051:052) RightParen |)| +//@[052:053) Dot |.| +//@[053:056) Identifier |bar| +//@[056:058) NewLine |\n\n| + +func constFunc() string => 'A' +//@[000:004) Identifier |func| +//@[005:014) Identifier |constFunc| +//@[014:015) LeftParen |(| +//@[015:016) RightParen |)| +//@[017:023) Identifier |string| +//@[024:026) Arrow |=>| +//@[027:030) StringComplete |'A'| +//@[030:031) NewLine |\n| +func funcWithOtherFuncRef() string => constFunc() +//@[000:004) Identifier |func| +//@[005:025) Identifier |funcWithOtherFuncRef| +//@[025:026) LeftParen |(| +//@[026:027) RightParen |)| +//@[028:034) Identifier |string| +//@[035:037) Arrow |=>| +//@[038:047) Identifier |constFunc| +//@[047:048) LeftParen |(| +//@[048:049) RightParen |)| +//@[049:051) NewLine |\n\n| + +func missingArgType(input) string => input +//@[000:004) Identifier |func| +//@[005:019) Identifier |missingArgType| +//@[019:020) LeftParen |(| +//@[020:025) Identifier |input| +//@[025:026) RightParen |)| +//@[027:033) Identifier |string| +//@[034:036) Arrow |=>| +//@[037:042) Identifier |input| +//@[042:044) NewLine |\n\n| + +func missingOutputType(input string) => input +//@[000:004) Identifier |func| +//@[005:022) Identifier |missingOutputType| +//@[022:023) LeftParen |(| +//@[023:028) Identifier |input| +//@[029:035) Identifier |string| +//@[035:036) RightParen |)| +//@[037:039) Arrow |=>| +//@[040:045) Identifier |input| +//@[045:047) NewLine |\n\n| + +func invalidType(input string) string => input +//@[000:004) Identifier |func| +//@[005:016) Identifier |invalidType| +//@[016:017) LeftParen |(| +//@[017:022) Identifier |input| +//@[023:029) Identifier |string| +//@[029:030) RightParen |)| +//@[031:037) Identifier |string| +//@[038:040) Arrow |=>| +//@[041:046) Identifier |input| +//@[046:048) NewLine |\n\n| + +output invalidType string = invalidType(true) +//@[000:006) Identifier |output| +//@[007:018) Identifier |invalidType| +//@[019:025) Identifier |string| +//@[026:027) Assignment |=| +//@[028:039) Identifier |invalidType| +//@[039:040) LeftParen |(| +//@[040:044) TrueKeyword |true| +//@[044:045) RightParen |)| +//@[045:047) NewLine |\n\n| + +func madeUpTypeArgs(a notAType, b alsoNotAType) string => '${a}-${b}' +//@[000:004) Identifier |func| +//@[005:019) Identifier |madeUpTypeArgs| +//@[019:020) LeftParen |(| +//@[020:021) Identifier |a| +//@[022:030) Identifier |notAType| +//@[030:031) Comma |,| +//@[032:033) Identifier |b| +//@[034:046) Identifier |alsoNotAType| +//@[046:047) RightParen |)| +//@[048:054) Identifier |string| +//@[055:057) Arrow |=>| +//@[058:061) StringLeftPiece |'${| +//@[061:062) Identifier |a| +//@[062:066) StringMiddlePiece |}-${| +//@[066:067) Identifier |b| +//@[067:069) StringRightPiece |}'| +//@[069:071) NewLine |\n\n| + +func noLambda('foo') string => '' +//@[000:004) Identifier |func| +//@[005:013) Identifier |noLambda| +//@[013:014) LeftParen |(| +//@[014:019) StringComplete |'foo'| +//@[019:020) RightParen |)| +//@[021:027) Identifier |string| +//@[028:030) Arrow |=>| +//@[031:033) StringComplete |''| +//@[033:035) NewLine |\n\n| + +func noLambda2 = (sdf 'foo') string => '' +//@[000:004) Identifier |func| +//@[005:014) Identifier |noLambda2| +//@[015:016) Assignment |=| +//@[017:018) LeftParen |(| +//@[018:021) Identifier |sdf| +//@[022:027) StringComplete |'foo'| +//@[027:028) RightParen |)| +//@[029:035) Identifier |string| +//@[036:038) Arrow |=>| +//@[039:041) StringComplete |''| +//@[041:043) NewLine |\n\n| + +func noLambda3 = string 'asdf' +//@[000:004) Identifier |func| +//@[005:014) Identifier |noLambda3| +//@[015:016) Assignment |=| +//@[017:023) Identifier |string| +//@[024:030) StringComplete |'asdf'| +//@[030:032) NewLine |\n\n| + +func argLengthMismatch(a string, b string, c string) array => ([a, b, c]) +//@[000:004) Identifier |func| +//@[005:022) Identifier |argLengthMismatch| +//@[022:023) LeftParen |(| +//@[023:024) Identifier |a| +//@[025:031) Identifier |string| +//@[031:032) Comma |,| +//@[033:034) Identifier |b| +//@[035:041) Identifier |string| +//@[041:042) Comma |,| +//@[043:044) Identifier |c| +//@[045:051) Identifier |string| +//@[051:052) RightParen |)| +//@[053:058) Identifier |array| +//@[059:061) Arrow |=>| +//@[062:063) LeftParen |(| +//@[063:064) LeftSquare |[| +//@[064:065) Identifier |a| +//@[065:066) Comma |,| +//@[067:068) Identifier |b| +//@[068:069) Comma |,| +//@[070:071) Identifier |c| +//@[071:072) RightSquare |]| +//@[072:073) RightParen |)| +//@[073:074) NewLine |\n| +var sdf = argLengthMismatch('asdf') +//@[000:003) Identifier |var| +//@[004:007) Identifier |sdf| +//@[008:009) Assignment |=| +//@[010:027) Identifier |argLengthMismatch| +//@[027:028) LeftParen |(| +//@[028:034) StringComplete |'asdf'| +//@[034:035) RightParen |)| +//@[035:037) NewLine |\n\n| + +var asdfwdf = noLambda('asd') +//@[000:003) Identifier |var| +//@[004:011) Identifier |asdfwdf| +//@[012:013) Assignment |=| +//@[014:022) Identifier |noLambda| +//@[022:023) LeftParen |(| +//@[023:028) StringComplete |'asd'| +//@[028:029) RightParen |)| +//@[029:031) NewLine |\n\n| + +func sayHello(name string) string => 'Hi ${name}!' +//@[000:004) Identifier |func| +//@[005:013) Identifier |sayHello| +//@[013:014) LeftParen |(| +//@[014:018) Identifier |name| +//@[019:025) Identifier |string| +//@[025:026) RightParen |)| +//@[027:033) Identifier |string| +//@[034:036) Arrow |=>| +//@[037:043) StringLeftPiece |'Hi ${| +//@[043:047) Identifier |name| +//@[047:050) StringRightPiece |}!'| +//@[050:051) NewLine |\n| +output hellos array = map(['Evie', 'Casper'], sayHello) // this syntax not supported currently, but should it be? +//@[000:006) Identifier |output| +//@[007:013) Identifier |hellos| +//@[014:019) Identifier |array| +//@[020:021) Assignment |=| +//@[022:025) Identifier |map| +//@[025:026) LeftParen |(| +//@[026:027) LeftSquare |[| +//@[027:033) StringComplete |'Evie'| +//@[033:034) Comma |,| +//@[035:043) StringComplete |'Casper'| +//@[043:044) RightSquare |]| +//@[044:045) Comma |,| +//@[046:054) Identifier |sayHello| +//@[054:055) RightParen |)| +//@[113:115) NewLine |\n\n| + +func sayHelloBadNewlines( +//@[000:004) Identifier |func| +//@[005:024) Identifier |sayHelloBadNewlines| +//@[024:025) LeftParen |(| +//@[025:026) NewLine |\n| + name string) string => 'Hi ${name}!' +//@[002:006) Identifier |name| +//@[007:013) Identifier |string| +//@[013:014) RightParen |)| +//@[015:021) Identifier |string| +//@[022:024) Arrow |=>| +//@[025:031) StringLeftPiece |'Hi ${| +//@[031:035) Identifier |name| +//@[035:038) StringRightPiece |}!'| +//@[038:040) NewLine |\n\n| + +type validStringLiteralUnion = 'foo'|'bar'|'baz' +//@[000:004) Identifier |type| +//@[005:028) Identifier |validStringLiteralUnion| +//@[029:030) Assignment |=| +//@[031:036) StringComplete |'foo'| +//@[036:037) Pipe ||| +//@[037:042) StringComplete |'bar'| +//@[042:043) Pipe ||| +//@[043:048) StringComplete |'baz'| +//@[048:049) NewLine |\n| +func invalidArgs(a validStringLiteralUnion, b string) string => a +//@[000:004) Identifier |func| +//@[005:016) Identifier |invalidArgs| +//@[016:017) LeftParen |(| +//@[017:018) Identifier |a| +//@[019:042) Identifier |validStringLiteralUnion| +//@[042:043) Comma |,| +//@[044:045) Identifier |b| +//@[046:052) Identifier |string| +//@[052:053) RightParen |)| +//@[054:060) Identifier |string| +//@[061:063) Arrow |=>| +//@[064:065) Identifier |a| +//@[065:066) NewLine |\n| +func invalidOutput() validStringLiteralUnion => 'foo' +//@[000:004) Identifier |func| +//@[005:018) Identifier |invalidOutput| +//@[018:019) LeftParen |(| +//@[019:020) RightParen |)| +//@[021:044) Identifier |validStringLiteralUnion| +//@[045:047) Arrow |=>| +//@[048:053) StringComplete |'foo'| +//@[053:054) NewLine |\n| + +//@[000:000) EndOfFile || diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep index abfecc48c88..75650017ec2 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.diagnostics.bicep @@ -60,44 +60,53 @@ module moduleWithConditionAndSelfCycle './main.bicep' = if ('foo' == 'bar') { module './main.bicep' = { //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| } module './main.bicep' = if (1 + 2 == 3) { //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| } module './main.bicep' = if //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[026:026) [BCP018 (Error)] Expected the "(" character at this location. (CodeDescription: none) || module './main.bicep' = if ( //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[028:028) [BCP009 (Error)] Expected a literal value, an array, an object, a parenthesized expression, or a function call at this location. (CodeDescription: none) || module './main.bicep' = if (true //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[032:032) [BCP018 (Error)] Expected the ")" character at this location. (CodeDescription: none) || module './main.bicep' = if (true) //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[033:033) [BCP018 (Error)] Expected the "{" character at this location. (CodeDescription: none) || module './main.bicep' = if { //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[027:028) [BCP018 (Error)] Expected the "(" character at this location. (CodeDescription: none) |{| } module './main.bicep' = if () { //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| //@[028:028) [BCP243 (Error)] Parentheses must contain exactly one expression. (CodeDescription: none) || } module './main.bicep' = if ('true') { //@[007:021) [BCP096 (Error)] Expected a module identifier at this location. (CodeDescription: none) |'./main.bicep'| +//@[007:021) [BCP094 (Error)] This module references itself, which is not allowed. (CodeDescription: none) |'./main.bicep'| } @@ -127,7 +136,9 @@ module modWithListKeysInCondition './main.bicep' = if (listKeys('foo', '2020-05- module modANoName './modulea.bicep' = if ({ 'a': b }.a == true) { //@[007:017) [BCP028 (Error)] Identifier "modANoName" is declared multiple times. Remove or rename the duplicates. (CodeDescription: none) |modANoName| +//@[007:017) [BCP035 (Error)] The specified "module" declaration is missing the following required properties: "name", "params". (CodeDescription: none) |modANoName| //@[044:047) [prefer-unquoted-property-names (Warning)] Property names that are valid identifiers should be declared without quotation marks and accessed using dot notation. (CodeDescription: bicep core(https://aka.ms/bicep/linter/prefer-unquoted-property-names)) |'a'| +//@[049:050) [BCP057 (Error)] The name "b" does not exist in the current context. (CodeDescription: none) |b| } diff --git a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep index 9027ba3cbc9..a0b8a06c445 100644 --- a/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/InvalidModules_LF/main.symbols.bicep @@ -115,7 +115,7 @@ module modWithListKeysInCondition './main.bicep' = if (listKeys('foo', '2020-05- module modANoName './modulea.bicep' = if ({ 'a': b }.a == true) { -//@[07:17) Module modANoName. Type: error. Declaration start char: 0, length: 68 +//@[07:17) Module modANoName. Type: module. Declaration start char: 0, length: 68 } diff --git a/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs b/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs index 0a580d16707..c6002b16a5f 100644 --- a/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs +++ b/src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs @@ -100,7 +100,8 @@ public void GetBuiltInConfiguration_NoParameter_ReturnsBuiltInConfigurationWithA ""resourceTypedParamsAndOutputs"": null, ""sourceMapping"": null, ""paramsFiles"": null, - ""userDefinedTypes"": null + ""userDefinedTypes"": null, + ""userDefinedFunctions"": null } }"); } @@ -158,7 +159,8 @@ public void GetBuiltInConfiguration_DisableAllAnalyzers_ReturnsBuiltInConfigurat ""resourceTypedParamsAndOutputs"": null, ""sourceMapping"": null, ""paramsFiles"": null, - ""userDefinedTypes"": null + ""userDefinedTypes"": null, + ""userDefinedFunctions"": null } }"); } @@ -241,7 +243,8 @@ public void GetBuiltInConfiguration_DisableAnalyzers_ReturnsBuiltInConfiguration ""resourceTypedParamsAndOutputs"": null, ""sourceMapping"": null, ""paramsFiles"": null, - ""userDefinedTypes"": null + ""userDefinedTypes"": null, + ""userDefinedFunctions"": null } }"); } @@ -546,7 +549,8 @@ public void GetConfiguration_ValidCustomConfiguration_OverridesBuiltInConfigurat ""resourceTypedParamsAndOutputs"": null, ""sourceMapping"": null, ""paramsFiles"": null, - ""userDefinedTypes"": null + ""userDefinedTypes"": null, + ""userDefinedFunctions"": null } }"); } diff --git a/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs b/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs index 8e9b80d3504..25aefb571f9 100644 --- a/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs +++ b/src/Bicep.Core.UnitTests/Features/FeatureProviderOverrides.cs @@ -16,6 +16,7 @@ public record FeatureProviderOverrides( bool? SourceMappingEnabled = default, bool? ParamsFilesEnabled = default, bool? UserDefinedTypesEnabled = default, + bool? UserDefinedFunctionsEnabled = default, string? AssemblyVersion = BicepTestConstants.DevAssemblyFileVersion) { public FeatureProviderOverrides(TestContext testContext, @@ -27,6 +28,7 @@ public FeatureProviderOverrides(TestContext testContext, bool? SourceMappingEnabled = default, bool? ParamsFilesEnabled = default, bool? UserDefinedTypesEnabled = default, + bool? UserDefinedFunctionsEnabled = default, string? AssemblyVersion = BicepTestConstants.DevAssemblyFileVersion - ) : this(FileHelper.GetCacheRootPath(testContext), RegistryEnabled, SymbolicNameCodegenEnabled, ExtensibilityEnabled, AdvancedListComprehensionEnabled, ResourceTypedParamsAndOutputsEnabled, SourceMappingEnabled, ParamsFilesEnabled, UserDefinedTypesEnabled, AssemblyVersion) {} + ) : this(FileHelper.GetCacheRootPath(testContext), RegistryEnabled, SymbolicNameCodegenEnabled, ExtensibilityEnabled, AdvancedListComprehensionEnabled, ResourceTypedParamsAndOutputsEnabled, SourceMappingEnabled, ParamsFilesEnabled, UserDefinedTypesEnabled, UserDefinedFunctionsEnabled, AssemblyVersion) {} } diff --git a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs index 6942f496e03..a949a749dd1 100644 --- a/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs +++ b/src/Bicep.Core.UnitTests/Features/OverriddenFeatureProvider.cs @@ -33,4 +33,6 @@ public OverriddenFeatureProvider(IFeatureProvider features, FeatureProviderOverr public bool ParamsFilesEnabled => overrides.ParamsFilesEnabled ?? features.ParamsFilesEnabled; public bool UserDefinedTypesEnabled => overrides.UserDefinedTypesEnabled ?? features.UserDefinedTypesEnabled; + + public bool UserDefinedFunctionsEnabled => overrides.UserDefinedFunctionsEnabled ?? features.UserDefinedFunctionsEnabled; } diff --git a/src/Bicep.Core/Analyzers/Linter/Rules/NoHardcodedLocationRule.cs b/src/Bicep.Core/Analyzers/Linter/Rules/NoHardcodedLocationRule.cs index d2d789a2790..943922cf56b 100644 --- a/src/Bicep.Core/Analyzers/Linter/Rules/NoHardcodedLocationRule.cs +++ b/src/Bicep.Core/Analyzers/Linter/Rules/NoHardcodedLocationRule.cs @@ -107,7 +107,7 @@ private void ValidateResourceLocationValue(List diagnostics, HashSe CodeFixKind.QuickFix, new CodeReplacement( definingVariable.DeclaringSyntax.Span, - $"param {definingVariable.Name} string = {definingVariable.Value.ToTextPreserveFormatting()}")); + $"param {definingVariable.Name} string = {definingVariable.DeclaringVariable.Value.ToTextPreserveFormatting()}")); diagnostics.Add(this.CreateFixableDiagnosticForSpan(diagnosticLevel, errorSpan, fix, msg)); } else diff --git a/src/Bicep.Core/Analyzers/Linter/Rules/UseStableVMImageRule.cs b/src/Bicep.Core/Analyzers/Linter/Rules/UseStableVMImageRule.cs index 1bd5a74dcf9..8676416d9cb 100644 --- a/src/Bicep.Core/Analyzers/Linter/Rules/UseStableVMImageRule.cs +++ b/src/Bicep.Core/Analyzers/Linter/Rules/UseStableVMImageRule.cs @@ -48,7 +48,7 @@ public override IEnumerable AnalyzeInternal(SemanticModel model, Di } else if (imageReferenceValue is VariableAccessSyntax && model.GetSymbolInfo(imageReferenceValue) is VariableSymbol variableSymbol && - variableSymbol.Value is ObjectSyntax variableValueSyntax) + variableSymbol.DeclaringVariable.Value is ObjectSyntax variableValueSyntax) { AddDiagnosticsIfImageReferencePropertiesContainPreview(diagnosticLevel, variableValueSyntax, diagnostics); } diff --git a/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs b/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs index 10177024128..200019324bc 100644 --- a/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs +++ b/src/Bicep.Core/Configuration/ExperimentalFeaturesEnabled.cs @@ -13,7 +13,8 @@ public record ExperimentalFeaturesEnabled( bool? ResourceTypedParamsAndOutputs, bool? SourceMapping, bool? ParamsFiles, - bool? UserDefinedTypes) + bool? UserDefinedTypes, + bool? UserDefinedFunctions) { public static ExperimentalFeaturesEnabled Bind(JsonElement element, string? configurationPath) => element.ToNonNullObject(); diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index e87f9d2e6fa..e406255a054 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -206,10 +206,10 @@ private static string BuildBicepConfigurationClause(string? configurationPath) = "BCP029", "The resource type is not valid. Specify a valid resource type of format \"@\"."); - public ErrorDiagnostic InvalidOutputType() => new( + public ErrorDiagnostic InvalidOutputType(IEnumerable validTypes) => new( TextSpan, "BCP030", - $"The output type is not valid. Please specify one of the following types: {ToQuotedString(LanguageConstants.DeclarationTypes.Keys)}."); + $"The output type is not valid. Please specify one of the following types: {ToQuotedString(validTypes)}."); public ErrorDiagnostic InvalidParameterType(IEnumerable validTypes) => new( TextSpan, @@ -1920,6 +1920,27 @@ public ErrorDiagnostic IndexOutOfBounds(string typeName, long tupleLength, long TextSpan, "BCP340", $"Unable to parse literal YAML value. Please ensure that it is well-formed."); + + public ErrorDiagnostic RuntimeValueNotAllowedInFunctionDeclaration(string? accessedSymbolName, IEnumerable? accessiblePropertyNames, IEnumerable? variableDependencyChain) + { + var variableDependencyChainClause = BuildVariableDependencyChainClause(variableDependencyChain); + var accessiblePropertiesClause = BuildAccessiblePropertiesClause(accessedSymbolName, accessiblePropertyNames); + + return new ErrorDiagnostic( + TextSpan, + "BCP341", + $"This expression is being used inside a function declaration, which requires a value that can be calculated at the start of the deployment.{variableDependencyChainClause}{accessiblePropertiesClause}"); + } + + public ErrorDiagnostic UserDefinedTypesNotAllowedInFunctionDeclaration() => new( + TextSpan, + "BCP342", + $"""User-defined types are not supported in user-defined function parameters or outputs."""); + + public ErrorDiagnostic FuncDeclarationStatementsUnsupported() => new( + TextSpan, + "BCP343", + $@"Using a func declaration statement requires enabling EXPERIMENTAL feature ""{nameof(ExperimentalFeaturesEnabled.UserDefinedFunctions)}""."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Emit/EmitConstants.cs b/src/Bicep.Core/Emit/EmitConstants.cs new file mode 100644 index 00000000000..bf8b162151e --- /dev/null +++ b/src/Bicep.Core/Emit/EmitConstants.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Core.Emit; + +public static class EmitConstants +{ + public const string UserDefinedFunctionsNamespace = "__bicep"; +} \ No newline at end of file diff --git a/src/Bicep.Core/Emit/EmitLimitationCalculator.cs b/src/Bicep.Core/Emit/EmitLimitationCalculator.cs index 07aca626650..a097d09da30 100644 --- a/src/Bicep.Core/Emit/EmitLimitationCalculator.cs +++ b/src/Bicep.Core/Emit/EmitLimitationCalculator.cs @@ -48,6 +48,8 @@ public static EmitLimitationInfo Calculate(SemanticModel model) BlockModuleOutputResourcePropertyAccess(model, diagnostics); BlockSafeDereferenceOfModuleOrResourceCollectionMember(model, diagnostics); BlockCyclicAggregateTypeReferences(model, diagnostics); + BlockUserDefinedFunctionsWithoutExperimentalFeaure(model, diagnostics); + BlockUserDefinedTypesWithUserDefinedFunctions(model, diagnostics); var paramAssignments = CalculateParameterAssignments(model, diagnostics); return new(diagnostics.GetDiagnostics(), moduleScopeData, resourceScopeData, paramAssignments); @@ -510,5 +512,42 @@ private static ImmutableDictionary CalculateP return generated.ToImmutableDictionary(); } + + private static void BlockUserDefinedFunctionsWithoutExperimentalFeaure(SemanticModel model, IDiagnosticWriter diagnostics) + { + foreach (var function in model.Root.FunctionDeclarations) + { + if (!model.Features.UserDefinedFunctionsEnabled) + { + diagnostics.Write(function.DeclaringFunction, x => x.FuncDeclarationStatementsUnsupported()); + } + } + } + + private static void BlockUserDefinedTypesWithUserDefinedFunctions(SemanticModel model, IDiagnosticWriter diagnostics) + { + foreach (var function in model.Root.FunctionDeclarations) + { + if (function.DeclaringFunction.Lambda is not TypedLambdaSyntax lambda) + { + continue; + } + + foreach (var localVariable in lambda.GetLocalVariables()) + { + var argTypeSymbol = model.GetSymbolInfo(localVariable.Type); + if (argTypeSymbol is not AmbientTypeSymbol and not ErrorSymbol) + { + diagnostics.Write(localVariable, x => x.UserDefinedTypesNotAllowedInFunctionDeclaration()); + } + } + + var outputTypeSymbol = model.GetSymbolInfo(lambda.ReturnType); + if (outputTypeSymbol is not AmbientTypeSymbol and not ErrorSymbol) + { + diagnostics.Write(lambda.ReturnType, x => x.UserDefinedTypesNotAllowedInFunctionDeclaration()); + } + } + } } } diff --git a/src/Bicep.Core/Emit/ExpressionConverter.cs b/src/Bicep.Core/Emit/ExpressionConverter.cs index 39658069193..f7f954f0771 100644 --- a/src/Bicep.Core/Emit/ExpressionConverter.cs +++ b/src/Bicep.Core/Emit/ExpressionConverter.cs @@ -95,6 +95,11 @@ public LanguageExpression ConvertExpression(Expression expression) function.Name, function.Parameters.Select(p => ConvertExpression(p))); + case UserDefinedFunctionCallExpression function: + return CreateFunction( + $"{EmitConstants.UserDefinedFunctionsNamespace}.{function.Name}", + function.Parameters.Select(p => ConvertExpression(p))); + case ResourceFunctionCallExpression listFunction when listFunction.Name.StartsWithOrdinalInsensitively(LanguageConstants.ListFunctionPrefix): { var resource = listFunction.Resource.Metadata; var resourceIdExpression = new PropertyAccessExpression( @@ -150,6 +155,10 @@ public LanguageExpression ConvertExpression(Expression expression) variableNames.Concat(body)); case LambdaVariableReferenceExpression exp: + if (exp.IsFunctionLambda) + { + return CreateFunction("parameters", new JTokenExpression(exp.Variable.Name)); + } return CreateFunction("lambdaVariables", new JTokenExpression(exp.Variable.Name)); case CopyIndexExpression exp: diff --git a/src/Bicep.Core/Emit/TemplateWriter.cs b/src/Bicep.Core/Emit/TemplateWriter.cs index 060866c1c59..95b06756540 100644 --- a/src/Bicep.Core/Emit/TemplateWriter.cs +++ b/src/Bicep.Core/Emit/TemplateWriter.cs @@ -120,6 +120,8 @@ public void Write(SourceAwareJsonTextWriter writer) this.EmitTypeDefinitionsIfPresent(jsonWriter, emitter); + this.EmitUserDefinedFunctions(emitter, program.Functions); + this.EmitParametersIfPresent(emitter, program.Parameters); this.EmitVariablesIfPresent(emitter, program.Variables); @@ -157,6 +159,27 @@ private void EmitTypeDefinitionsIfPresent(PositionTrackingJsonTextWriter jsonWri jsonWriter.WriteEndObject(); } + private void EmitUserDefinedFunctions(ExpressionEmitter emitter, ImmutableArray functions) + { + if (!functions.Any()) + { + return; + } + + emitter.EmitArrayProperty("functions", () => { + emitter.EmitObject(() => { + emitter.EmitProperty("namespace", EmitConstants.UserDefinedFunctionsNamespace); + + emitter.EmitObjectProperty("members", () => { + foreach (var function in functions) + { + EmitUserDefinedFunction(emitter, function); + } + }); + }); + }); + } + private void EmitParametersIfPresent(ExpressionEmitter emitter, ImmutableArray parameters) { if (!parameters.Any()) @@ -220,6 +243,36 @@ private void EmitParameter(ExpressionEmitter emitter, DeclaredParameterExpressio }, parameter.SourceSyntax); } + private void EmitUserDefinedFunction(ExpressionEmitter emitter, DeclaredFunctionExpression function) + { + if (function.Lambda is not LambdaExpression lambda) + { + throw new ArgumentException("Invalid function expression lambda encountered."); + } + + emitter.EmitObjectProperty(function.Name, () => + { + emitter.EmitArrayProperty("parameters", () => { + for (var i = 0; i < lambda.Parameters.Length; i++) + { + var parameterObject = TypePropertiesForTypeExpression(lambda.ParameterTypes[i]!); + parameterObject = parameterObject.MergeProperty("name", new StringLiteralExpression(null, lambda.Parameters[i])); + + emitter.EmitObject(() => { + EmitProperties(emitter, parameterObject); + }); + } + }); + + emitter.EmitObjectProperty("output", () => { + var outputObject = TypePropertiesForTypeExpression(lambda.OutputType!); + outputObject = outputObject.MergeProperty("value", lambda.Body); + + EmitProperties(emitter, outputObject); + }); + }, function.SourceSyntax); + } + private void EmitProperties(ExpressionEmitter emitter, ObjectExpression objectExpression) { foreach (var property in objectExpression.Properties) diff --git a/src/Bicep.Core/Features/FeatureProvider.cs b/src/Bicep.Core/Features/FeatureProvider.cs index 32d4165cdde..cdb4d38b68b 100644 --- a/src/Bicep.Core/Features/FeatureProvider.cs +++ b/src/Bicep.Core/Features/FeatureProvider.cs @@ -34,6 +34,8 @@ public FeatureProvider(RootConfiguration configuration) public bool UserDefinedTypesEnabled => configuration.ExperimentalFeaturesEnabled.UserDefinedTypes ?? false; + public bool UserDefinedFunctionsEnabled => configuration.ExperimentalFeaturesEnabled.UserDefinedFunctions ?? false; + public static bool TracingEnabled => ReadBooleanEnvVar("BICEP_TRACING_ENABLED", defaultValue: false); public static TraceVerbosity TracingVerbosity => ReadEnumEnvvar("BICEP_TRACING_VERBOSITY", TraceVerbosity.Basic); diff --git a/src/Bicep.Core/Features/IFeatureProvider.cs b/src/Bicep.Core/Features/IFeatureProvider.cs index 9b4ba2bc12f..3f522d5f989 100644 --- a/src/Bicep.Core/Features/IFeatureProvider.cs +++ b/src/Bicep.Core/Features/IFeatureProvider.cs @@ -22,5 +22,7 @@ public interface IFeatureProvider bool ParamsFilesEnabled { get; } bool UserDefinedTypesEnabled { get; } + + bool UserDefinedFunctionsEnabled { get; } } } diff --git a/src/Bicep.Core/Intermediate/Expression.cs b/src/Bicep.Core/Intermediate/Expression.cs index 044360d6586..ede60cb673c 100644 --- a/src/Bicep.Core/Intermediate/Expression.cs +++ b/src/Bicep.Core/Intermediate/Expression.cs @@ -291,7 +291,8 @@ public override void Accept(IExpressionVisitor visitor) public record LambdaVariableReferenceExpression( SyntaxBase? SourceSyntax, - LocalVariableSymbol Variable + LocalVariableSymbol Variable, + bool IsFunctionLambda ) : Expression(SourceSyntax) { public override void Accept(IExpressionVisitor visitor) @@ -334,7 +335,9 @@ public override void Accept(IExpressionVisitor visitor) public record LambdaExpression( SyntaxBase? SourceSyntax, ImmutableArray Parameters, - Expression Body + ImmutableArray ParameterTypes, + Expression Body, + SyntaxBase? OutputType ) : Expression(SourceSyntax) { public override void Accept(IExpressionVisitor visitor) @@ -446,6 +449,7 @@ public record ProgramExpression( ImmutableArray Imports, ImmutableArray Parameters, ImmutableArray Variables, + ImmutableArray Functions, ImmutableArray Resources, ImmutableArray Modules, ImmutableArray Outputs @@ -464,3 +468,27 @@ ImmutableArray AdditionalProperties public override void Accept(IExpressionVisitor visitor) => visitor.VisitAccessChainExpression(this); } + +public record DeclaredFunctionExpression( + SyntaxBase? SourceSyntax, + string Name, + Expression Lambda +) : Expression(SourceSyntax) +{ + public override void Accept(IExpressionVisitor visitor) + => visitor.VisitDeclaredFunctionExpression(this); + + protected override object? GetDebugAttributes() => new { Name }; +} + +public record UserDefinedFunctionCallExpression( + SyntaxBase? SourceSyntax, + string Name, + ImmutableArray Parameters +) : Expression(SourceSyntax) +{ + public override void Accept(IExpressionVisitor visitor) + => visitor.VisitUserDefinedFunctionCallExpression(this); + + protected override object? GetDebugAttributes() => new { Name }; +} \ No newline at end of file diff --git a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs index 976111bbc56..40a4df42c97 100644 --- a/src/Bicep.Core/Intermediate/ExpressionBuilder.cs +++ b/src/Bicep.Core/Intermediate/ExpressionBuilder.cs @@ -127,12 +127,23 @@ private Expression ConvertWithoutLowering(SyntaxBase syntax) case ResourceAccessSyntax resourceAccess: return ConvertResourceAccess(resourceAccess); case LambdaSyntax lambda: - var variableNames = lambda.GetLocalVariables().Select(x => x.Name.IdentifierName); + var variables = lambda.GetLocalVariables(); return new LambdaExpression( lambda, - variableNames.ToImmutableArray(), - ConvertWithoutLowering(lambda.Body)); + variables.Select(x => x.Name.IdentifierName).ToImmutableArray(), + variables.Select(x => null).ToImmutableArray(), + ConvertWithoutLowering(lambda.Body), + null); + case TypedLambdaSyntax lambda: + var typedVariables = lambda.GetLocalVariables(); + + return new LambdaExpression( + lambda, + typedVariables.Select(x => x.Name.IdentifierName).ToImmutableArray(), + typedVariables.Select(x => x.Type).ToImmutableArray(), + ConvertWithoutLowering(lambda.Body), + lambda.ReturnType); case ForSyntax forSyntax: return new ForLoopExpression( @@ -175,6 +186,12 @@ private Expression ConvertWithoutLowering(SyntaxBase syntax) variable.Name.IdentifierName, ConvertWithoutLowering(variable.Value)); + case FunctionDeclarationSyntax function: + return new DeclaredFunctionExpression( + function, + function.Name.IdentifierName, + ConvertWithoutLowering(function.Lambda)); + case OutputDeclarationSyntax output: return new DeclaredOutputExpression( output, @@ -262,12 +279,18 @@ private ProgramExpression ConvertProgram(ProgramSyntax syntax) .OfType() .ToImmutableArray(); + var functions = Context.SemanticModel.Root.FunctionDeclarations + .Select(x => ConvertWithoutLowering(x.DeclaringSyntax)) + .OfType() + .ToImmutableArray(); + return new ProgramExpression( syntax, metadataArray, imports, parameters, functionVariables.AddRange(variables), + functions, resources, modules, outputs); @@ -501,6 +524,14 @@ private Expression ConvertFunction(FunctionCallSyntaxBase functionCall) return new SynthesizedVariableReferenceExpression(functionCall, functionVariable.Name); } + if (Context.SemanticModel.GetSymbolInfo(functionCall) is DeclaredFunctionSymbol declaredFunction) + { + return new UserDefinedFunctionCallExpression( + functionCall, + functionCall.Name.IdentifierName, + functionCall.Arguments.Select(a => ConvertWithoutLowering(a.Expression)).ToImmutableArray()); + } + if (Context.SemanticModel.TypeManager.GetMatchedFunctionResultValue(functionCall) is {} functionValue) { return functionValue; @@ -712,8 +743,10 @@ private Expression GetLocalVariableExpression(SyntaxBase sourceSyntax, LocalVari { case ForSyntax @for: return GetLoopVariable(localVariableSymbol, @for, new CopyIndexExpression(sourceSyntax, GetCopyIndexName(@for))); - case LambdaSyntax lambda: - return new LambdaVariableReferenceExpression(sourceSyntax, localVariableSymbol); + case LambdaSyntax: + return new LambdaVariableReferenceExpression(sourceSyntax, localVariableSymbol, IsFunctionLambda: false); + case TypedLambdaSyntax: + return new LambdaVariableReferenceExpression(sourceSyntax, localVariableSymbol, IsFunctionLambda: true); } throw new NotImplementedException($"{nameof(LocalVariableSymbol)} was declared by an unexpected syntax type '{enclosingSyntax?.GetType().Name}'."); diff --git a/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs b/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs index 5fd8bfe1289..da106963733 100644 --- a/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs +++ b/src/Bicep.Core/Intermediate/ExpressionRewriteVisitor.cs @@ -272,6 +272,24 @@ public virtual Expression ReplaceDeclaredVariableExpression(DeclaredVariableExpr return hasChanges ? expression with { Value = value } : expression; } + void IExpressionVisitor.VisitDeclaredFunctionExpression(DeclaredFunctionExpression expression) => ReplaceCurrent(expression, ReplaceDeclaredFunctionExpression); + public virtual Expression ReplaceDeclaredFunctionExpression(DeclaredFunctionExpression expression) + { + var hasChanges = + TryRewrite(expression.Lambda, out var lambda); + + return hasChanges ? expression with { Lambda = lambda } : expression; + } + + void IExpressionVisitor.VisitUserDefinedFunctionCallExpression(UserDefinedFunctionCallExpression expression) => ReplaceCurrent(expression, ReplaceUserDefinedFunctionCallExpression); + public virtual Expression ReplaceUserDefinedFunctionCallExpression(UserDefinedFunctionCallExpression expression) + { + var hasChanges = + TryRewrite(expression.Parameters, out var parameters); + + return hasChanges ? expression with { Parameters = parameters } : expression; + } + void IExpressionVisitor.VisitDeclaredOutputExpression(DeclaredOutputExpression expression) => ReplaceCurrent(expression, ReplaceDeclaredOutputExpression); public virtual Expression ReplaceDeclaredOutputExpression(DeclaredOutputExpression expression) { @@ -319,11 +337,12 @@ public virtual Expression ReplaceProgramExpression(ProgramExpression expression) TryRewriteStrict(expression.Imports, out var imports) | TryRewriteStrict(expression.Parameters, out var parameters) | TryRewriteStrict(expression.Variables, out var variables) | + TryRewriteStrict(expression.Functions, out var functions) | TryRewriteStrict(expression.Resources, out var resources) | TryRewriteStrict(expression.Modules, out var modules) | TryRewriteStrict(expression.Outputs, out var outputs); - return hasChanges ? expression with { Metadata = metadata, Imports = imports, Parameters = parameters, Variables = variables, Resources = resources, Modules = modules, Outputs = outputs } : expression; + return hasChanges ? expression with { Metadata = metadata, Imports = imports, Parameters = parameters, Variables = variables, Functions = functions, Resources = resources, Modules = modules, Outputs = outputs } : expression; } protected Expression Replace(Expression expression) diff --git a/src/Bicep.Core/Intermediate/ExpressionVisitor.cs b/src/Bicep.Core/Intermediate/ExpressionVisitor.cs index f38cd756441..27b83b08eb4 100644 --- a/src/Bicep.Core/Intermediate/ExpressionVisitor.cs +++ b/src/Bicep.Core/Intermediate/ExpressionVisitor.cs @@ -188,12 +188,23 @@ public void VisitResourceDependencyExpression(ResourceDependencyExpression expre Visit(expression.Reference); } + public void VisitDeclaredFunctionExpression(DeclaredFunctionExpression expression) + { + Visit(expression.Lambda); + } + + public void VisitUserDefinedFunctionCallExpression(UserDefinedFunctionCallExpression expression) + { + Visit(expression.Parameters); + } + public void VisitProgramExpression(ProgramExpression expression) { Visit(expression.Metadata); Visit(expression.Imports); Visit(expression.Parameters); Visit(expression.Variables); + Visit(expression.Functions); Visit(expression.Resources); Visit(expression.Modules); Visit(expression.Outputs); diff --git a/src/Bicep.Core/Intermediate/IExpressionVisitor.cs b/src/Bicep.Core/Intermediate/IExpressionVisitor.cs index 41365382b28..22e1c74cbcb 100644 --- a/src/Bicep.Core/Intermediate/IExpressionVisitor.cs +++ b/src/Bicep.Core/Intermediate/IExpressionVisitor.cs @@ -78,4 +78,8 @@ public interface IExpressionVisitor void VisitProgramExpression(ProgramExpression expression); void VisitAccessChainExpression(AccessChainExpression expression); + + void VisitDeclaredFunctionExpression(DeclaredFunctionExpression expression); + + void VisitUserDefinedFunctionCallExpression(UserDefinedFunctionCallExpression expression); } diff --git a/src/Bicep.Core/LanguageConstants.cs b/src/Bicep.Core/LanguageConstants.cs index 195608d6162..2734c784bbe 100644 --- a/src/Bicep.Core/LanguageConstants.cs +++ b/src/Bicep.Core/LanguageConstants.cs @@ -56,6 +56,7 @@ public static class LanguageConstants public const string VariableKeyword = "var"; public const string ResourceKeyword = "resource"; public const string ModuleKeyword = "module"; + public const string FunctionKeyword = "func"; public const string ExistingKeyword = "existing"; public const string ImportKeyword = "import"; public const string WithKeyword = "with"; diff --git a/src/Bicep.Core/Parsing/BaseParser.cs b/src/Bicep.Core/Parsing/BaseParser.cs index e0e5abeabab..ae3c1329280 100644 --- a/src/Bicep.Core/Parsing/BaseParser.cs +++ b/src/Bicep.Core/Parsing/BaseParser.cs @@ -1084,6 +1084,26 @@ private SyntaxBase ParenthesizedExpressionOrLambda(ExpressionFlags expressionFla return GetParenthesizedExpressionSyntax(openParen, expressionsOrCommas, closeParen); } + private SyntaxBase TypedLocalVariable(params TokenType[] terminatingTypes) + { + var name = IdentifierOrSkip(b => b.ExpectedVariableIdentifier()); + var type = this.WithRecovery(() => Type(allowOptionalResourceType: false), RecoveryFlags.None, terminatingTypes); + + return new TypedLocalVariableSyntax(name, type); + } + + protected SyntaxBase TypedLambda() + { + var (openParen, expressionsOrCommas, closeParen) = ParenthesizedExpressionList(() => TypedLocalVariable(TokenType.NewLine, TokenType.Comma, TokenType.RightParen)); + + var returnType = this.WithRecovery(() => Type(allowOptionalResourceType: false), RecoveryFlags.None, TokenType.NewLine, TokenType.RightParen); + var arrow = this.WithRecovery(() => Expect(TokenType.Arrow, b => b.ExpectedCharacter("=>")), RecoveryFlags.None, TokenType.NewLine, TokenType.RightParen); + var expression = this.WithRecovery(() => this.Expression(ExpressionFlags.AllowComplexLiterals), RecoveryFlags.None, TokenType.NewLine, TokenType.RightParen); + var variableBlock = new TypedVariableBlockSyntax(openParen, expressionsOrCommas, closeParen); + + return new TypedLambdaSyntax(variableBlock, returnType, arrow, expression); + } + private SyntaxBase PrimaryExpression(ExpressionFlags expressionFlags) { Token nextToken = this.reader.Peek(); diff --git a/src/Bicep.Core/Parsing/Parser.cs b/src/Bicep.Core/Parsing/Parser.cs index 0402461a980..fc679f36e4c 100644 --- a/src/Bicep.Core/Parsing/Parser.cs +++ b/src/Bicep.Core/Parsing/Parser.cs @@ -64,6 +64,7 @@ protected override SyntaxBase Declaration() => LanguageConstants.TypeKeyword => this.TypeDeclaration(leadingNodes), LanguageConstants.ParameterKeyword => this.ParameterDeclaration(leadingNodes), LanguageConstants.VariableKeyword => this.VariableDeclaration(leadingNodes), + LanguageConstants.FunctionKeyword => this.FunctionDeclaration(leadingNodes), LanguageConstants.ResourceKeyword => this.ResourceDeclaration(leadingNodes), LanguageConstants.OutputKeyword => this.OutputDeclaration(leadingNodes), LanguageConstants.ModuleKeyword => this.ModuleDeclaration(leadingNodes), @@ -158,6 +159,15 @@ private SyntaxBase VariableDeclaration(IEnumerable leadingNodes) return new VariableDeclarationSyntax(leadingNodes, keyword, name, assignment, value); } + private SyntaxBase FunctionDeclaration(IEnumerable leadingNodes) + { + var keyword = ExpectKeyword(LanguageConstants.FunctionKeyword); + var name = this.IdentifierWithRecovery(b => b.ExpectedVariableIdentifier(), RecoveryFlags.None, TokenType.Assignment, TokenType.NewLine); + var lambda = this.WithRecovery(() => this.TypedLambda(), GetSuppressionFlag(name), TokenType.NewLine); + + return new FunctionDeclarationSyntax(leadingNodes, keyword, name, lambda); + } + private SyntaxBase OutputDeclaration(IEnumerable leadingNodes) { var keyword = ExpectKeyword(LanguageConstants.OutputKeyword); diff --git a/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs b/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs index 610777cd349..b84dde66f4b 100644 --- a/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs +++ b/src/Bicep.Core/PrettyPrint/DocumentBuildVisitor.cs @@ -130,6 +130,24 @@ public override void VisitParameterDeclarationSyntax(ParameterDeclarationSyntax this.Visit(syntax.Modifier); }); + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) => + this.BuildStatement(syntax, () => + { + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Keyword); + this.Visit(syntax.Name); + this.Visit(syntax.Lambda); + }, children => { + var leadingNodes = children[0..^3]; + var keyword = children[^3]; + var name = children[^2]; + var lambda = children[^1]; + + return Spread( + Concat(leadingNodes.Concat(keyword)), + Concat(name, lambda)); + }); + public override void VisitVariableDeclarationSyntax(VariableDeclarationSyntax syntax) => this.BuildStatement(syntax, () => { @@ -246,17 +264,21 @@ public override void VisitVariableBlockSyntax(VariableBlockSyntax syntax) => this.Visit(syntax.CloseParen); }); - public override void VisitLambdaSyntax(LambdaSyntax syntax) => - this.Build(() => base.VisitLambdaSyntax(syntax), children => - { - Debug.Assert(children.Length == 3); + public override void VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax) => + this.BuildWithConcat(() => { + this.Visit(syntax.OpenParen); + this.VisitCommaAndNewLineSeparated(syntax.Children, leadingAndTrailingSpace: false); + this.Visit(syntax.CloseParen); + }); - ILinkedDocument token = children[0]; - ILinkedDocument arrow = children[1]; - ILinkedDocument body = children[2]; + public override void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) => + this.BuildWithSpread(() => base.VisitTypedLocalVariableSyntax(syntax)); + + public override void VisitLambdaSyntax(LambdaSyntax syntax) => + this.BuildWithSpread(() => base.VisitLambdaSyntax(syntax)); - return Spread(token, arrow, body); - }); + public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) => + this.BuildWithSpread(() => base.VisitTypedLambdaSyntax(syntax)); private void VisitCommaAndNewLineSeparated(ImmutableArray nodes, bool leadingAndTrailingSpace) { @@ -642,7 +664,7 @@ private static ILinkedDocument Spread(IEnumerable combinators) private void BuildWithSpread(Action visitAciton) => this.Build(visitAciton, Spread); - private void BuildStatement(SyntaxBase syntax, Action visitAction) + private void BuildStatement(SyntaxBase syntax, Action visitAction, Func buildFunc) { if (this.HasSyntaxError(syntax)) { @@ -654,7 +676,11 @@ private void BuildStatement(SyntaxBase syntax, Action visitAction) return; } - this.Build(visitAction, children => + this.Build(visitAction, buildFunc); + } + + private void BuildStatement(SyntaxBase syntax, Action visitAction) + => BuildStatement(syntax, visitAction, children => { var splitIndex = Array.IndexOf(children, Nil); @@ -664,7 +690,6 @@ private void BuildStatement(SyntaxBase syntax, Action visitAction) return Spread(head.AsEnumerable().Concat(tail)); }); - } private void Build(Action visitAction, Func buildFunc) { diff --git a/src/Bicep.Core/Semantics/Binder.cs b/src/Bicep.Core/Semantics/Binder.cs index 0dbf67aba3c..aae40cec45c 100644 --- a/src/Bicep.Core/Semantics/Binder.cs +++ b/src/Bicep.Core/Semantics/Binder.cs @@ -22,18 +22,16 @@ public Binder(INamespaceProvider namespaceProvider, IFeatureProvider features, B // TODO use lazy or some other pattern for init this.bicepFile = sourceFile; this.TargetScope = SyntaxHelper.GetTargetScope(sourceFile); - var (declarations, outermostScopes) = DeclarationVisitor.GetDeclarations(namespaceProvider, features, TargetScope, sourceFile, symbolContext); - var uniqueDeclarations = GetUniqueDeclarations(declarations); - this.NamespaceResolver = GetNamespaceResolver(features, namespaceProvider, sourceFile, this.TargetScope, uniqueDeclarations); - this.Bindings = NameBindingVisitor.GetBindings(sourceFile.ProgramSyntax, uniqueDeclarations, NamespaceResolver, outermostScopes); + var fileScope = DeclarationVisitor.GetDeclarations(namespaceProvider, features, TargetScope, sourceFile, symbolContext); + this.NamespaceResolver = GetNamespaceResolver(features, namespaceProvider, sourceFile, this.TargetScope, fileScope); + this.Bindings = NameBindingVisitor.GetBindings(sourceFile.ProgramSyntax, NamespaceResolver, fileScope); this.cyclesBySymbol = CyclicCheckVisitor.FindCycles(sourceFile.ProgramSyntax, this.Bindings); this.FileSymbol = new FileSymbol( symbolContext, sourceFile, NamespaceResolver, - outermostScopes, - declarations); + fileScope); } public ResourceScope TargetScope { get; } @@ -60,22 +58,9 @@ public bool IsDescendant(SyntaxBase node, SyntaxBase potentialAncestor) public ImmutableArray? TryGetCycle(DeclaredSymbol declaredSymbol) => this.cyclesBySymbol.TryGetValue(declaredSymbol, out var cycle) ? cycle : null; - private static ImmutableDictionary GetUniqueDeclarations(IEnumerable outermostDeclarations) + private static NamespaceResolver GetNamespaceResolver(IFeatureProvider features, INamespaceProvider namespaceProvider, BicepSourceFile sourceFile, ResourceScope targetScope, ILanguageScope fileScope) { - // in cases of duplicate declarations we will see multiple declaration symbols in the result list - // for simplicitly we will bind to the first one - // it may cause follow-on type errors, but there will also be errors about duplicate identifiers as well - return outermostDeclarations - .OrderBy(x => x is not OutputSymbol && x is not MetadataSymbol ? 0 : 1) - .ToLookup(x => x.Name, LanguageConstants.IdentifierComparer) - .ToImmutableDictionary(x => x.Key, x => x.First(), LanguageConstants.IdentifierComparer); - } - - private static NamespaceResolver GetNamespaceResolver(IFeatureProvider features, INamespaceProvider namespaceProvider, BicepSourceFile sourceFile, ResourceScope targetScope, ImmutableDictionary uniqueDeclarations) - { - var importedNamespaces = uniqueDeclarations.Values.OfType(); - - return NamespaceResolver.Create(features, namespaceProvider, sourceFile, targetScope, importedNamespaces); + return NamespaceResolver.Create(features, namespaceProvider, sourceFile, targetScope, fileScope); } } } diff --git a/src/Bicep.Core/Semantics/DeclarationVisitor.cs b/src/Bicep.Core/Semantics/DeclarationVisitor.cs index 8150d99eae5..0f437bab2f3 100644 --- a/src/Bicep.Core/Semantics/DeclarationVisitor.cs +++ b/src/Bicep.Core/Semantics/DeclarationVisitor.cs @@ -23,32 +23,39 @@ public sealed class DeclarationVisitor : AstVisitor private readonly ResourceScope targetScope; private readonly ISymbolContext context; - private readonly IList declarations; - - private readonly IList childScopes; + private readonly IList localScopes; private readonly Stack activeScopes = new(); - private DeclarationVisitor(INamespaceProvider namespaceProvider, IFeatureProvider features, ResourceScope targetScope, ISymbolContext context, IList declarations, IList childScopes) + private DeclarationVisitor(INamespaceProvider namespaceProvider, IFeatureProvider features, ResourceScope targetScope, ISymbolContext context, IList localScopes) { this.namespaceProvider = namespaceProvider; this.features = features; this.targetScope = targetScope; this.context = context; - this.declarations = declarations; - this.childScopes = childScopes; + this.localScopes = localScopes; } // Returns the list of top level declarations as well as top level scopes. - public static (ImmutableArray, ImmutableArray) GetDeclarations(INamespaceProvider namespaceProvider, IFeatureProvider features, ResourceScope targetScope, BicepSourceFile sourceFile, ISymbolContext symbolContext) + public static LocalScope GetDeclarations(INamespaceProvider namespaceProvider, IFeatureProvider features, ResourceScope targetScope, BicepSourceFile sourceFile, ISymbolContext symbolContext) { // collect declarations - var declarations = new List(); - var childScopes = new List(); - var declarationVisitor = new DeclarationVisitor(namespaceProvider, features, targetScope, symbolContext, declarations, childScopes); + var localScopes = new List(); + var declarationVisitor = new DeclarationVisitor(namespaceProvider, features, targetScope, symbolContext, localScopes); declarationVisitor.Visit(sourceFile.ProgramSyntax); - return (declarations.ToImmutableArray(), childScopes.Select(MakeImmutable).ToImmutableArray()); + return MakeImmutable(localScopes.Single()); + } + + public override void VisitProgramSyntax(ProgramSyntax syntax) + { + // create new scope without any descendants + var scope = new LocalScope(string.Empty, syntax, syntax, ImmutableArray.Empty, ImmutableArray.Empty, ScopeResolution.GlobalsOnly); + this.PushScope(scope); + + base.VisitProgramSyntax(syntax); + + this.PopScope(); } public override void VisitMetadataDeclarationSyntax(MetadataDeclarationSyntax syntax) @@ -79,7 +86,15 @@ public override void VisitVariableDeclarationSyntax(VariableDeclarationSyntax sy { base.VisitVariableDeclarationSyntax(syntax); - var symbol = new VariableSymbol(this.context, syntax.Name.IdentifierName, syntax, syntax.Value); + var symbol = new VariableSymbol(this.context, syntax.Name.IdentifierName, syntax); + DeclareSymbol(symbol); + } + + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + base.VisitFunctionDeclarationSyntax(syntax); + + var symbol = new DeclaredFunctionSymbol(this.context, syntax.Name.IdentifierName, syntax); DeclareSymbol(symbol); } @@ -92,7 +107,7 @@ public override void VisitResourceDeclarationSyntax(ResourceDeclarationSyntax sy // and the actual object body (for-loop). That's OK, in that case, this scope will // be empty and we'll use the `for` scope for lookups. var bindingSyntax = syntax.Value is IfConditionSyntax ifConditionSyntax ? ifConditionSyntax.Body : syntax.Value; - var scope = new LocalScope(string.Empty, syntax, bindingSyntax, ImmutableArray.Empty, ImmutableArray.Empty); + var scope = new LocalScope(string.Empty, syntax, bindingSyntax, ImmutableArray.Empty, ImmutableArray.Empty, ScopeResolution.InheritParent); this.PushScope(scope); base.VisitResourceDeclarationSyntax(syntax); @@ -164,7 +179,7 @@ public override void VisitParameterAssignmentSyntax(ParameterAssignmentSyntax sy public override void VisitLambdaSyntax(LambdaSyntax syntax) { // create new scope without any descendants - var scope = new LocalScope(string.Empty, syntax, syntax.Body, ImmutableArray.Empty, ImmutableArray.Empty); + var scope = new LocalScope(string.Empty, syntax, syntax.Body, ImmutableArray.Empty, ImmutableArray.Empty, ScopeResolution.InheritParent); this.PushScope(scope); /* @@ -183,10 +198,32 @@ public override void VisitLambdaSyntax(LambdaSyntax syntax) this.PopScope(); } + public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) + { + // create new scope without any descendants + var scope = new LocalScope(string.Empty, syntax, syntax.Body, ImmutableArray.Empty, ImmutableArray.Empty, ScopeResolution.GlobalsOnly); + this.PushScope(scope); + + /* + * We cannot add the local symbol to the list of declarations because it will + * break name binding at the global namespace level + */ + foreach (var variable in syntax.GetLocalVariables()) + { + var itemVariableSymbol = new LocalVariableSymbol(this.context, variable.Name.IdentifierName, variable, LocalKind.LambdaItemVariable); + DeclareSymbol(itemVariableSymbol); + } + + // visit the children + base.VisitTypedLambdaSyntax(syntax); + + this.PopScope(); + } + public override void VisitForSyntax(ForSyntax syntax) { // create new scope without any descendants - var scope = new LocalScope(string.Empty, syntax, syntax.Body, ImmutableArray.Empty, ImmutableArray.Empty); + var scope = new LocalScope(string.Empty, syntax, syntax.Body, ImmutableArray.Empty, ImmutableArray.Empty, ScopeResolution.InheritParent); this.PushScope(scope); /* @@ -219,10 +256,6 @@ private void DeclareSymbol(DeclaredSymbol symbol) { current.Locals.Add(symbol); } - else - { - this.declarations.Add(symbol); - } } private void PushScope(LocalScope scope) @@ -242,7 +275,7 @@ private void PushScope(LocalScope scope) else { // add this to the root list - this.childScopes.Add(item); + this.localScopes.Add(item); } this.activeScopes.Push(item); diff --git a/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs b/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs new file mode 100644 index 00000000000..6b9b206a340 --- /dev/null +++ b/src/Bicep.Core/Semantics/DeclaredFunctionSymbol.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Bicep.Core.Extensions; +using Bicep.Core.Syntax; +using Bicep.Core.TypeSystem; + +namespace Bicep.Core.Semantics; + +public class DeclaredFunctionSymbol : DeclaredSymbol, IFunctionSymbol +{ + public DeclaredFunctionSymbol(ISymbolContext context, string name, FunctionDeclarationSyntax declaringSyntax) + : base(context, name, declaringSyntax, declaringSyntax.Name) + { + this.overloadsLazy = new(() => GetFunctionOverload() is {} overload ? + ImmutableArray.Create(overload) : + ImmutableArray.Empty); + } + + private readonly Lazy> overloadsLazy; + + public FunctionDeclarationSyntax DeclaringFunction => (FunctionDeclarationSyntax)this.DeclaringSyntax; + + public override void Accept(SymbolVisitor visitor) => visitor.VisitDeclaredFunctionSymbol(this); + + public override SymbolKind Kind => SymbolKind.Function; + + public override IEnumerable Descendants => Type.AsEnumerable(); + + public ImmutableArray Overloads => overloadsLazy.Value; + + public FunctionFlags FunctionFlags => FunctionFlags.Default; + + private FunctionOverload GetFunctionOverload() + { + var builder = new FunctionOverloadBuilder(this.Name); + if (SemanticModelHelper.TryGetDescription(Context.Binder, Context.TypeManager, DeclaringFunction) is {} description) + { + builder.WithGenericDescription(description); + } + + if (this.DeclaringFunction.Lambda is not TypedLambdaSyntax lambdaSyntax || + this.Context.TypeManager.GetDeclaredType(this.DeclaringFunction.Lambda) is not LambdaType lambdaType) + { + builder.WithReturnType(ErrorType.Empty()); + + return builder.Build(); + } + + var localVariables = lambdaSyntax.GetLocalVariables().ToImmutableArray(); + for (var i = 0; i < localVariables.Length; i++) + { + builder.WithRequiredParameter(localVariables[i].Name.IdentifierName, lambdaType.ArgumentTypes[i].Type, ""); + } + + builder.WithReturnType(lambdaType.ReturnType.Type); + + return builder.Build(); + } +} \ No newline at end of file diff --git a/src/Bicep.Core/Semantics/FileSymbol.cs b/src/Bicep.Core/Semantics/FileSymbol.cs index d5efdbb512d..313b9cd1942 100644 --- a/src/Bicep.Core/Semantics/FileSymbol.cs +++ b/src/Bicep.Core/Semantics/FileSymbol.cs @@ -25,8 +25,7 @@ public FileSymbol( ISymbolContext context, BicepSourceFile sourceFile, NamespaceResolver namespaceResolver, - IEnumerable outermostScopes, - ImmutableArray declarations) + LocalScope fileScope) : base(sourceFile.FileUri.LocalPath) { this.Context = context; @@ -34,19 +33,20 @@ public FileSymbol( this.NamespaceResolver = namespaceResolver; this.FileUri = sourceFile.FileUri; this.FileKind = sourceFile.FileKind; - this.LocalScopes = outermostScopes.ToImmutableArray(); - - // TODO: Avoid looping 8 times? - this.DeclarationsBySyntax = declarations.ToImmutableDictionary(x => x.DeclaringSyntax); - this.ImportDeclarations = declarations.OfType().ToImmutableArray(); - this.MetadataDeclarations = declarations.OfType().ToImmutableArray(); - this.ParameterDeclarations = declarations.OfType().ToImmutableArray(); - this.TypeDeclarations = declarations.OfType().ToImmutableArray(); - this.VariableDeclarations = declarations.OfType().ToImmutableArray(); - this.ResourceDeclarations = declarations.OfType().ToImmutableArray(); - this.ModuleDeclarations = declarations.OfType().ToImmutableArray(); - this.OutputDeclarations = declarations.OfType().ToImmutableArray(); - this.ParameterAssignments = declarations.OfType().ToImmutableArray(); + this.LocalScopes = fileScope.ChildScopes; + + // TODO: Avoid looping 9 times? + this.DeclarationsBySyntax = fileScope.Declarations.ToImmutableDictionary(x => x.DeclaringSyntax); + this.ImportDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.MetadataDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.ParameterDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.TypeDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.VariableDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.FunctionDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.ResourceDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.ModuleDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.OutputDeclarations = fileScope.Declarations.OfType().ToImmutableArray(); + this.ParameterAssignments = fileScope.Declarations.OfType().ToImmutableArray(); this.declarationsByName = this.Declarations.ToLookup(decl => decl.Name, LanguageConstants.IdentifierComparer); @@ -61,6 +61,7 @@ public FileSymbol( .Concat(this.ParameterDeclarations) .Concat(this.TypeDeclarations) .Concat(this.VariableDeclarations) + .Concat(this.FunctionDeclarations) .Concat(this.ResourceDeclarations) .Concat(this.ModuleDeclarations) .Concat(this.OutputDeclarations) @@ -94,6 +95,8 @@ public FileSymbol( public ImmutableArray VariableDeclarations { get; } + public ImmutableArray FunctionDeclarations { get; } + public ImmutableArray ResourceDeclarations { get; } public ImmutableArray ModuleDeclarations { get; } @@ -111,6 +114,8 @@ public FileSymbol( /// public IEnumerable Declarations => this.Descendants.OfType(); + public ScopeResolution ScopeResolution => ScopeResolution.GlobalsOnly; + public override void Accept(SymbolVisitor visitor) { visitor.VisitFileSymbol(this); diff --git a/src/Bicep.Core/Semantics/FunctionSymbol.cs b/src/Bicep.Core/Semantics/FunctionSymbol.cs index a2064b24fd7..51e6942de7c 100644 --- a/src/Bicep.Core/Semantics/FunctionSymbol.cs +++ b/src/Bicep.Core/Semantics/FunctionSymbol.cs @@ -8,7 +8,7 @@ namespace Bicep.Core.Semantics { - public class FunctionSymbol : Symbol + public class FunctionSymbol : Symbol, IFunctionSymbol { public FunctionSymbol(ObjectType declaringObject, string name, IEnumerable overloads) : base(name) diff --git a/src/Bicep.Core/Semantics/IFunctionSymbol.cs b/src/Bicep.Core/Semantics/IFunctionSymbol.cs new file mode 100644 index 00000000000..f89059e6b3f --- /dev/null +++ b/src/Bicep.Core/Semantics/IFunctionSymbol.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Immutable; +using Bicep.Core.TypeSystem; + +namespace Bicep.Core.Semantics; + +public interface IFunctionSymbol +{ + string Name { get; } + + ImmutableArray Overloads { get; } + + FunctionFlags FunctionFlags { get; } +} \ No newline at end of file diff --git a/src/Bicep.Core/Semantics/ILanguageScope.cs b/src/Bicep.Core/Semantics/ILanguageScope.cs index ba2f86f2f8a..72422e782c1 100644 --- a/src/Bicep.Core/Semantics/ILanguageScope.cs +++ b/src/Bicep.Core/Semantics/ILanguageScope.cs @@ -10,5 +10,7 @@ public interface ILanguageScope IEnumerable GetDeclarationsByName(string name); IEnumerable Declarations { get; } + + ScopeResolution ScopeResolution { get; } } } diff --git a/src/Bicep.Core/Semantics/ISymbolContext.cs b/src/Bicep.Core/Semantics/ISymbolContext.cs index ed1786eb24d..573c7886364 100644 --- a/src/Bicep.Core/Semantics/ISymbolContext.cs +++ b/src/Bicep.Core/Semantics/ISymbolContext.cs @@ -8,5 +8,7 @@ public interface ISymbolContext ITypeManager TypeManager { get; } Compilation Compilation { get; } + + IBinder Binder { get; } } } diff --git a/src/Bicep.Core/Semantics/LocalScope.cs b/src/Bicep.Core/Semantics/LocalScope.cs index beaa8a1967f..41eb98bc36c 100644 --- a/src/Bicep.Core/Semantics/LocalScope.cs +++ b/src/Bicep.Core/Semantics/LocalScope.cs @@ -12,11 +12,12 @@ namespace Bicep.Core.Semantics /// public class LocalScope : Symbol, ILanguageScope { - public LocalScope(string name, SyntaxBase declaringSyntax, SyntaxBase bindingSyntax, IEnumerable locals, IEnumerable childScopes) + public LocalScope(string name, SyntaxBase declaringSyntax, SyntaxBase bindingSyntax, IEnumerable locals, IEnumerable childScopes, ScopeResolution scopeResolution) : base(name) { this.DeclaringSyntax = declaringSyntax; this.BindingSyntax = bindingSyntax; + this.ScopeResolution = scopeResolution; this.Locals = locals.ToImmutableArray(); this.ChildScopes = childScopes.ToImmutableArray(); } @@ -42,12 +43,14 @@ public LocalScope(string name, SyntaxBase declaringSyntax, SyntaxBase bindingSyn public override IEnumerable Descendants => this.ChildScopes.Concat(this.Locals); - public LocalScope ReplaceLocals(IEnumerable newLocals) => new(this.Name, this.DeclaringSyntax, this.BindingSyntax, newLocals, this.ChildScopes); + public LocalScope ReplaceLocals(IEnumerable newLocals) => new(this.Name, this.DeclaringSyntax, this.BindingSyntax, newLocals, this.ChildScopes, this.ScopeResolution); - public LocalScope ReplaceChildren(IEnumerable newChildren) => new(this.Name, this.DeclaringSyntax, this.BindingSyntax, this.Locals, newChildren); + public LocalScope ReplaceChildren(IEnumerable newChildren) => new(this.Name, this.DeclaringSyntax, this.BindingSyntax, this.Locals, newChildren, this.ScopeResolution); public IEnumerable GetDeclarationsByName(string name) => this.Locals.Where(symbol => symbol.NameSource.IsValid && string.Equals(symbol.Name, name, LanguageConstants.IdentifierComparison)).ToList(); public IEnumerable Declarations => this.Locals; + + public ScopeResolution ScopeResolution { get; } } } diff --git a/src/Bicep.Core/Semantics/LocalVariableSymbol.cs b/src/Bicep.Core/Semantics/LocalVariableSymbol.cs index 7c07624eb42..a7c527c90e8 100644 --- a/src/Bicep.Core/Semantics/LocalVariableSymbol.cs +++ b/src/Bicep.Core/Semantics/LocalVariableSymbol.cs @@ -11,14 +11,22 @@ public LocalVariableSymbol(ISymbolContext context, string name, LocalVariableSyn : base(context, name, declaringSyntax, declaringSyntax.Name) { this.LocalKind = localKind; + this.DeclaredTypeSyntax = null; } - public LocalVariableSyntax DeclaringLocalVariable => (LocalVariableSyntax)this.DeclaringSyntax; + public LocalVariableSymbol(ISymbolContext context, string name, TypedLocalVariableSyntax declaringSyntax, LocalKind localKind) + : base(context, name, declaringSyntax, declaringSyntax.Name) + { + this.LocalKind = localKind; + this.DeclaredTypeSyntax = declaringSyntax.Type; + } public override void Accept(SymbolVisitor visitor) => visitor.VisitLocalVariableSymbol(this); public override SymbolKind Kind => SymbolKind.Local; + public SyntaxBase? DeclaredTypeSyntax; + public LocalKind LocalKind { get; } } } diff --git a/src/Bicep.Core/Semantics/NameBindingVisitor.cs b/src/Bicep.Core/Semantics/NameBindingVisitor.cs index 82e41d2fe0f..4270e3bf434 100644 --- a/src/Bicep.Core/Semantics/NameBindingVisitor.cs +++ b/src/Bicep.Core/Semantics/NameBindingVisitor.cs @@ -18,8 +18,6 @@ public sealed class NameBindingVisitor : AstVisitor { private FunctionFlags allowedFlags; - private readonly IReadOnlyDictionary declarations; - private readonly IDictionary bindings; private readonly NamespaceResolver namespaceResolver; @@ -29,12 +27,10 @@ public sealed class NameBindingVisitor : AstVisitor private readonly Stack activeScopes; private NameBindingVisitor( - IReadOnlyDictionary declarations, IDictionary bindings, NamespaceResolver namespaceResolver, ImmutableDictionary allLocalScopes) { - this.declarations = declarations; this.bindings = bindings; this.namespaceResolver = namespaceResolver; this.allLocalScopes = allLocalScopes; @@ -43,14 +39,13 @@ private NameBindingVisitor( public static ImmutableDictionary GetBindings( ProgramSyntax programSyntax, - IReadOnlyDictionary outermostDeclarations, NamespaceResolver namespaceResolver, - ImmutableArray childScopes) + LocalScope fileScope) { // bind identifiers to declarations var bindings = new Dictionary(); - var allLocalScopes = ScopeCollectorVisitor.Build(childScopes); - var binder = new NameBindingVisitor(outermostDeclarations, bindings, namespaceResolver, allLocalScopes); + var allLocalScopes = ScopeCollectorVisitor.Build(ImmutableArray.Create(fileScope)); + var binder = new NameBindingVisitor(bindings, namespaceResolver, allLocalScopes); binder.Visit(programSyntax); return bindings.ToImmutableDictionary(); @@ -60,14 +55,6 @@ public override void VisitProgramSyntax(ProgramSyntax syntax) { base.VisitProgramSyntax(syntax); - // create bindings for all of the declarations to their corresponding symbol - // this is needed to make find all references work correctly - // (doing this here to avoid side-effects in the constructor) - foreach (DeclaredSymbol declaredSymbol in this.declarations.Values) - { - this.bindings.Add(declaredSymbol.DeclaringSyntax, declaredSymbol); - } - // include all the locals in the symbol table as well // since we only allow lookups by object and not by name, // a flat symbol table should be sufficient @@ -202,6 +189,17 @@ public override void VisitVariableDeclarationSyntax(VariableDeclarationSyntax sy allowedFlags = FunctionFlags.Default; } + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + allowedFlags = FunctionFlags.FunctionDecorator; + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Keyword); + this.Visit(syntax.Name); + allowedFlags = FunctionFlags.RequiresInlining; + this.Visit(syntax.Lambda); + allowedFlags = FunctionFlags.Default; + } + public override void VisitOutputDeclarationSyntax(OutputDeclarationSyntax syntax) { allowedFlags = FunctionFlags.OutputDecorator; @@ -314,12 +312,6 @@ private Symbol LookupSymbolByName(IdentifierSyntax identifierSyntax, bool isFunc private Symbol? LookupLocalSymbolByName(IdentifierSyntax identifierSyntax, bool isFunctionCall) { - if (isFunctionCall) - { - // functions can't be local symbols - return null; - } - // iterating over a stack gives you the items in the same // order as if you popped each one but without modifying the stack foreach (var scope in activeScopes) @@ -332,6 +324,12 @@ private Symbol LookupSymbolByName(IdentifierSyntax identifierSyntax, bool isFunc // found a symbol - return it return symbol; } + + if (scope.ScopeResolution == ScopeResolution.GlobalsOnly) + { + // don't inherit outer scope variables + break; + } } return null; @@ -356,11 +354,6 @@ private Symbol LookupGlobalSymbolByName(IdentifierSyntax identifierSyntax, bool // There might be instances where a variable declaration for example uses the same name as one of the imported // functions, in this case to differentiate a variable declaration vs a function access we check the namespace value, // the former case must have an empty namespace value whereas the latter will have a namespace value. - if (this.declarations.TryGetValue(identifierSyntax.IdentifierName, out var globalSymbol)) - { - // we found the symbol in the global namespace - return globalSymbol; - } // attempt to find name in the built in namespaces. imported namespaces will be present in the declarations list as they create declared symbols. if (this.namespaceResolver.BuiltIns.TryGetValue(identifierSyntax.IdentifierName) is { } namespaceSymbol) @@ -379,7 +372,7 @@ private Symbol LookupGlobalSymbolByName(IdentifierSyntax identifierSyntax, bool // if no types were found, fall back to checking against imported functions to show a more relevant error message if a function name is used as an uninvoked symbol var foundType = foundTypes.FirstOrDefault() ?? FindFunctionMatchesInAllNamespaces(identifierSyntax).FirstOrDefault(); - return SymbolValidator.ResolveUnqualifiedSymbol(foundType, identifierSyntax, namespaceResolver, declarations.Keys); + return SymbolValidator.ResolveUnqualifiedSymbol(foundType, identifierSyntax, namespaceResolver); } // attempt to find function in all imported namespaces diff --git a/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs b/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs index 7429216079b..1d6f0c86a63 100644 --- a/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs +++ b/src/Bicep.Core/Semantics/Namespaces/NamespaceResolver.cs @@ -23,8 +23,11 @@ private NamespaceResolver(ImmutableDictionary namespaceTy this.BuiltIns = builtIns; } - public static NamespaceResolver Create(IFeatureProvider features, INamespaceProvider namespaceProvider, BicepSourceFile sourceFile, ResourceScope targetScope, IEnumerable importedNamespaces) + public static NamespaceResolver Create(IFeatureProvider features, INamespaceProvider namespaceProvider, BicepSourceFile sourceFile, ResourceScope targetScope, ILanguageScope fileScope) { + var importedNamespaces = fileScope.Declarations.OfType() + .DistinctBy(x => x.Name, LanguageConstants.IdentifierComparer); + var builtInNamespaceSymbols = new Dictionary(LanguageConstants.IdentifierComparer); var namespaceTypes = importedNamespaces .Select(x => x.DeclaredType) diff --git a/src/Bicep.Core/Semantics/ScopeResolution.cs b/src/Bicep.Core/Semantics/ScopeResolution.cs new file mode 100644 index 00000000000..7280a6b64c5 --- /dev/null +++ b/src/Bicep.Core/Semantics/ScopeResolution.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Core.Semantics; + +public enum ScopeResolution +{ + /// + /// Inherit scoped symbols from the parent scope. + /// + InheritParent, + + /// + /// Only symbols that have not been declared by a parent (or above) scope. + /// + GlobalsOnly, +} \ No newline at end of file diff --git a/src/Bicep.Core/Semantics/SemanticDiagnosticVisitor.cs b/src/Bicep.Core/Semantics/SemanticDiagnosticVisitor.cs index d51d2bbcabe..6dd460faa22 100644 --- a/src/Bicep.Core/Semantics/SemanticDiagnosticVisitor.cs +++ b/src/Bicep.Core/Semantics/SemanticDiagnosticVisitor.cs @@ -75,6 +75,12 @@ public override void VisitVariableSymbol(VariableSymbol symbol) this.CollectDiagnostics(symbol); } + public override void VisitDeclaredFunctionSymbol(DeclaredFunctionSymbol symbol) + { + base.VisitDeclaredFunctionSymbol(symbol); + this.CollectDiagnostics(symbol); + } + public override void VisitResourceSymbol(ResourceSymbol symbol) { base.VisitResourceSymbol(symbol); diff --git a/src/Bicep.Core/Semantics/SemanticModelHelper.cs b/src/Bicep.Core/Semantics/SemanticModelHelper.cs index a5df72456a8..37bfb1332a4 100644 --- a/src/Bicep.Core/Semantics/SemanticModelHelper.cs +++ b/src/Bicep.Core/Semantics/SemanticModelHelper.cs @@ -42,12 +42,12 @@ function.DeclaringObject is NamespaceType namespaceType && } public static string? TryGetDescription(SemanticModel semanticModel, DecorableSyntax decorable) - => TryGetDescription(semanticModel.Binder, semanticModel.TypeManager.GetDeclaredType, decorable); + => TryGetDescription(semanticModel.Binder, semanticModel.TypeManager, decorable); - public static string? TryGetDescription(IBinder binder, Func getDeclaredTypeFunc, DecorableSyntax decorable) + public static string? TryGetDescription(IBinder binder, ITypeManager typeManager, DecorableSyntax decorable) { var decorator = SemanticModelHelper.TryGetDecoratorInNamespace(binder, - getDeclaredTypeFunc, + typeManager.GetDeclaredType, decorable, SystemNamespaceType.BuiltInName, LanguageConstants.MetadataDescriptionPropertyName); diff --git a/src/Bicep.Core/Semantics/SymbolContext.cs b/src/Bicep.Core/Semantics/SymbolContext.cs index 5c0a918ce26..3efa4659bf8 100644 --- a/src/Bicep.Core/Semantics/SymbolContext.cs +++ b/src/Bicep.Core/Semantics/SymbolContext.cs @@ -16,32 +16,22 @@ public SymbolContext(Compilation compilation, SemanticModel semanticModel) this.semanticModel = semanticModel; } - public ITypeManager TypeManager - { - get - { - this.CheckLock(); - return this.semanticModel.TypeManager; - } - } + public ITypeManager TypeManager => WithLockCheck(() => this.semanticModel.TypeManager); - public Compilation Compilation - { - get - { - this.CheckLock(); - return this.compilation; - } - } + public Compilation Compilation => WithLockCheck(() => this.compilation); + + public IBinder Binder => WithLockCheck(() => this.semanticModel.Binder); public void Unlock() => this.unlocked = true; - private void CheckLock() + private T WithLockCheck(Func getFunc) { - if (this.unlocked == false) + if (!this.unlocked) { throw new InvalidOperationException("Properties of the symbol context should not be accessed until name binding is completed."); } + + return getFunc(); } } } diff --git a/src/Bicep.Core/Semantics/SymbolExtensions.cs b/src/Bicep.Core/Semantics/SymbolExtensions.cs index 1b0dde2c1c7..9bb36dc6c0d 100644 --- a/src/Bicep.Core/Semantics/SymbolExtensions.cs +++ b/src/Bicep.Core/Semantics/SymbolExtensions.cs @@ -51,7 +51,7 @@ private static bool HasDecorator(DecorableSyntax decorable, string decoratorName /// The function symbol to inspect /// The index of the function argument /// Function to look up the assigned type of a given argument - public static TypeSymbol GetDeclaredArgumentType(this FunctionSymbol functionSymbol, int argIndex, GetFunctionArgumentType? getAssignedArgumentType = null) + public static TypeSymbol GetDeclaredArgumentType(this IFunctionSymbol functionSymbol, int argIndex, GetFunctionArgumentType? getAssignedArgumentType = null) { // if we have a mix of wildcard and non-wildcard overloads, prioritize the non-wildcard overloads. // the wildcards have super generic type definitions, so don't result in helpful completions. diff --git a/src/Bicep.Core/Semantics/SymbolValidator.cs b/src/Bicep.Core/Semantics/SymbolValidator.cs index 52cdf1debf7..1a8a52c8374 100644 --- a/src/Bicep.Core/Semantics/SymbolValidator.cs +++ b/src/Bicep.Core/Semantics/SymbolValidator.cs @@ -72,7 +72,7 @@ public static Symbol ResolveUnqualifiedFunction(FunctionFlags allowedFlags, Symb _ => builder.SymbolicNameDoesNotExistWithSuggestion(identifierSyntax.IdentifierName, suggestedName), }); - public static Symbol ResolveUnqualifiedSymbol(Symbol? foundSymbol, IdentifierSyntax identifierSyntax, NamespaceResolver namespaceResolver, IEnumerable declarations) + public static Symbol ResolveUnqualifiedSymbol(Symbol? foundSymbol, IdentifierSyntax identifierSyntax, NamespaceResolver namespaceResolver) => ResolveSymbolInternal( FunctionFlags.Default, foundSymbol, diff --git a/src/Bicep.Core/Semantics/SymbolVisitor.cs b/src/Bicep.Core/Semantics/SymbolVisitor.cs index af60f39ed4b..1fd222d005a 100644 --- a/src/Bicep.Core/Semantics/SymbolVisitor.cs +++ b/src/Bicep.Core/Semantics/SymbolVisitor.cs @@ -41,6 +41,11 @@ public virtual void VisitVariableSymbol(VariableSymbol symbol) VisitDescendants(symbol); } + public virtual void VisitDeclaredFunctionSymbol(DeclaredFunctionSymbol symbol) + { + VisitDescendants(symbol); + } + public virtual void VisitResourceSymbol(ResourceSymbol symbol) { VisitDescendants(symbol); diff --git a/src/Bicep.Core/Semantics/VariableSymbol.cs b/src/Bicep.Core/Semantics/VariableSymbol.cs index 0a21ab9dacc..39bc6bede79 100644 --- a/src/Bicep.Core/Semantics/VariableSymbol.cs +++ b/src/Bicep.Core/Semantics/VariableSymbol.cs @@ -7,16 +7,13 @@ namespace Bicep.Core.Semantics { public class VariableSymbol : DeclaredSymbol { - public VariableSymbol(ISymbolContext context, string name, VariableDeclarationSyntax declaringSyntax, SyntaxBase value) + public VariableSymbol(ISymbolContext context, string name, VariableDeclarationSyntax declaringSyntax) : base(context, name, declaringSyntax, declaringSyntax.Name) { - this.Value = value; } public VariableDeclarationSyntax DeclaringVariable => (VariableDeclarationSyntax)this.DeclaringSyntax; - public SyntaxBase Value { get; } - public override void Accept(SymbolVisitor visitor) => visitor.VisitVariableSymbol(this); public override SymbolKind Kind => SymbolKind.Variable; diff --git a/src/Bicep.Core/Syntax/AstVisitor.cs b/src/Bicep.Core/Syntax/AstVisitor.cs index 42291685c25..ae46b573a17 100644 --- a/src/Bicep.Core/Syntax/AstVisitor.cs +++ b/src/Bicep.Core/Syntax/AstVisitor.cs @@ -348,5 +348,30 @@ public override void VisitNonNullAssertionSyntax(NonNullAssertionSyntax syntax) { this.Visit(syntax.BaseExpression); } + + public override void VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax) + { + this.VisitNodes(syntax.Children); + } + + public override void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) + { + this.Visit(syntax.Name); + this.Visit(syntax.Type); + } + + public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) + { + this.Visit(syntax.VariableSection); + this.Visit(syntax.ReturnType); + this.Visit(syntax.Body); + } + + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Name); + this.Visit(syntax.Lambda); + } } } diff --git a/src/Bicep.Core/Syntax/CstVisitor.cs b/src/Bicep.Core/Syntax/CstVisitor.cs index 61ed9bd3696..9d760faa293 100644 --- a/src/Bicep.Core/Syntax/CstVisitor.cs +++ b/src/Bicep.Core/Syntax/CstVisitor.cs @@ -430,5 +430,34 @@ public override void VisitNonNullAssertionSyntax(NonNullAssertionSyntax syntax) this.Visit(syntax.BaseExpression); this.Visit(syntax.AssertionOperator); } + + public override void VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax) + { + this.Visit(syntax.OpenParen); + this.VisitNodes(syntax.Children); + this.Visit(syntax.CloseParen); + } + + public override void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) + { + this.Visit(syntax.Name); + this.Visit(syntax.Type); + } + + public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) + { + this.Visit(syntax.VariableSection); + this.Visit(syntax.ReturnType); + this.Visit(syntax.Arrow); + this.Visit(syntax.Body); + } + + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + this.VisitNodes(syntax.LeadingNodes); + this.Visit(syntax.Keyword); + this.Visit(syntax.Name); + this.Visit(syntax.Lambda); + } } } diff --git a/src/Bicep.Core/Syntax/FunctionDeclarationSyntax.cs b/src/Bicep.Core/Syntax/FunctionDeclarationSyntax.cs new file mode 100644 index 00000000000..10db174ebb3 --- /dev/null +++ b/src/Bicep.Core/Syntax/FunctionDeclarationSyntax.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Linq; +using Bicep.Core.Navigation; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class FunctionDeclarationSyntax : StatementSyntax, ITopLevelNamedDeclarationSyntax +{ + public FunctionDeclarationSyntax(IEnumerable leadingNodes, Token keyword, IdentifierSyntax name, SyntaxBase lambda) + : base(leadingNodes) + { + AssertKeyword(keyword, nameof(keyword), LanguageConstants.FunctionKeyword); + AssertSyntaxType(name, nameof(name), typeof(IdentifierSyntax)); + + this.Keyword = keyword; + this.Name = name; + this.Lambda = lambda; + } + + public Token Keyword { get; } + + public IdentifierSyntax Name { get; } + + public SyntaxBase Lambda { get; } + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitFunctionDeclarationSyntax(this); + + public override TextSpan Span => TextSpan.Between(this.LeadingNodes.FirstOrDefault() ?? Keyword, Lambda); +} diff --git a/src/Bicep.Core/Syntax/ISymbolNameSource.cs b/src/Bicep.Core/Syntax/ISymbolNameSource.cs index 28829f7b563..622abb2283e 100644 --- a/src/Bicep.Core/Syntax/ISymbolNameSource.cs +++ b/src/Bicep.Core/Syntax/ISymbolNameSource.cs @@ -2,11 +2,6 @@ // Licensed under the MIT License. using Bicep.Core.Parsing; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Bicep.Core.Syntax { diff --git a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs index ab17faa2f9a..fe56821adaa 100644 --- a/src/Bicep.Core/Syntax/ISyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/ISyntaxVisitor.cs @@ -119,5 +119,13 @@ public interface ISyntaxVisitor void VisitLambdaSyntax(LambdaSyntax syntax); void VisitNonNullAssertionSyntax(NonNullAssertionSyntax syntax); + + void VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax); + + void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax); + + void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax); + + void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax); } } diff --git a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs index 25c1bc50d25..a0c3d12aefa 100644 --- a/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxRewriteVisitor.cs @@ -199,7 +199,8 @@ protected virtual SyntaxBase VisitUsingDeclarationSyntax(UsingDeclarationSyntax protected virtual SyntaxBase ReplaceVariableDeclarationSyntax(VariableDeclarationSyntax syntax) { - var hasChanges = TryRewriteStrict(syntax.Keyword, out var keyword); + var hasChanges = TryRewrite(syntax.LeadingNodes, out var leadingNodes); + hasChanges |= TryRewriteStrict(syntax.Keyword, out var keyword); hasChanges |= TryRewriteStrict(syntax.Name, out var name); hasChanges |= TryRewrite(syntax.Assignment, out var assignment); hasChanges |= TryRewrite(syntax.Value, out var value); @@ -209,7 +210,7 @@ protected virtual SyntaxBase ReplaceVariableDeclarationSyntax(VariableDeclaratio return syntax; } - return new VariableDeclarationSyntax(keyword, name, assignment, value); + return new VariableDeclarationSyntax(leadingNodes, keyword, name, assignment, value); } void ISyntaxVisitor.VisitVariableDeclarationSyntax(VariableDeclarationSyntax syntax) => ReplaceCurrent(syntax, ReplaceVariableDeclarationSyntax); @@ -947,5 +948,64 @@ protected virtual SyntaxBase ReplaceNonNullAssertionSyntax(NonNullAssertionSynta return new NonNullAssertionSyntax(baseExpression, assertionOperator); } void ISyntaxVisitor.VisitNonNullAssertionSyntax(NonNullAssertionSyntax syntax) => ReplaceCurrent(syntax, ReplaceNonNullAssertionSyntax); + + protected virtual SyntaxBase ReplaceTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax) + { + var hasChanges = TryRewriteStrict(syntax.OpenParen, out var openParen); + hasChanges |= TryRewriteStrict(syntax.Children, out var children); + hasChanges |= TryRewrite(syntax.CloseParen, out var closeParen); + + if (!hasChanges) + { + return syntax; + } + + return new TypedVariableBlockSyntax(openParen, children, closeParen); + } + void ISyntaxVisitor.VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax) => ReplaceCurrent(syntax, ReplaceTypedVariableBlockSyntax); + + protected virtual SyntaxBase ReplaceTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) + { + var hasChanges = TryRewriteStrict(syntax.Name, out var name); + hasChanges |= TryRewriteStrict(syntax.Type, out var type); + if (!hasChanges) + { + return syntax; + } + + return new TypedLocalVariableSyntax(name, type); + } + void ISyntaxVisitor.VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) => ReplaceCurrent(syntax, ReplaceTypedLocalVariableSyntax); + + protected virtual SyntaxBase ReplaceTypedLambdaSyntax(TypedLambdaSyntax syntax) + { + var hasChanges = TryRewriteStrict(syntax.VariableSection, out var variableSection); + hasChanges |= TryRewrite(syntax.ReturnType, out var returnType); + hasChanges |= TryRewriteStrict(syntax.Arrow, out var arrow); + hasChanges |= TryRewrite(syntax.Body, out var body); + if (!hasChanges) + { + return syntax; + } + + return new TypedLambdaSyntax(variableSection, returnType, arrow, body); + } + void ISyntaxVisitor.VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) => ReplaceCurrent(syntax, ReplaceTypedLambdaSyntax); + + protected virtual SyntaxBase ReplaceFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + var hasChanges = TryRewrite(syntax.LeadingNodes, out var leadingNodes); + hasChanges |= TryRewriteStrict(syntax.Keyword, out var keyword); + hasChanges |= TryRewriteStrict(syntax.Name, out var name); + hasChanges |= TryRewrite(syntax.Lambda, out var lambda); + + if (!hasChanges) + { + return syntax; + } + + return new FunctionDeclarationSyntax(leadingNodes, keyword, name, lambda); + } + void ISyntaxVisitor.VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) => ReplaceCurrent(syntax, ReplaceFunctionDeclarationSyntax); } } diff --git a/src/Bicep.Core/Syntax/SyntaxVisitor.cs b/src/Bicep.Core/Syntax/SyntaxVisitor.cs index 82e33029a3e..1f93aebaf6a 100644 --- a/src/Bicep.Core/Syntax/SyntaxVisitor.cs +++ b/src/Bicep.Core/Syntax/SyntaxVisitor.cs @@ -125,6 +125,14 @@ public abstract class SyntaxVisitor : ISyntaxVisitor public abstract void VisitVariableDeclarationSyntax(VariableDeclarationSyntax syntax); + public abstract void VisitTypedVariableBlockSyntax(TypedVariableBlockSyntax syntax); + + public abstract void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax); + + public abstract void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax); + + public abstract void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax); + public void Visit(SyntaxBase? node) { if (node == null) diff --git a/src/Bicep.Core/Syntax/TypedLambdaSyntax.cs b/src/Bicep.Core/Syntax/TypedLambdaSyntax.cs new file mode 100644 index 00000000000..6ab9b35dce2 --- /dev/null +++ b/src/Bicep.Core/Syntax/TypedLambdaSyntax.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Linq; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class TypedLambdaSyntax : ExpressionSyntax +{ + public TypedLambdaSyntax(SyntaxBase variableSection, SyntaxBase returnType, SyntaxBase arrow, SyntaxBase body) + { + this.VariableSection = variableSection; + this.ReturnType = returnType; + this.Arrow = arrow; + this.Body = body; + } + + public SyntaxBase VariableSection { get; } + + public SyntaxBase ReturnType { get; } + + public SyntaxBase Arrow { get; } + + public SyntaxBase Body { get; } + + public IEnumerable GetLocalVariables() + => VariableSection switch { + TypedVariableBlockSyntax vars => vars.Arguments, + _ => Enumerable.Empty(), + }; + + public override TextSpan Span => TextSpan.Between(this.VariableSection, this.Body); + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitTypedLambdaSyntax(this); +} \ No newline at end of file diff --git a/src/Bicep.Core/Syntax/TypedLocalVariableSyntax.cs b/src/Bicep.Core/Syntax/TypedLocalVariableSyntax.cs new file mode 100644 index 00000000000..c3dbc2a2755 --- /dev/null +++ b/src/Bicep.Core/Syntax/TypedLocalVariableSyntax.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Navigation; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class TypedLocalVariableSyntax : SyntaxBase, INamedDeclarationSyntax +{ + public TypedLocalVariableSyntax(IdentifierSyntax name, SyntaxBase type) + { + this.Name = name; + this.Type = type; + } + + public IdentifierSyntax Name { get; } + + public SyntaxBase Type { get; } + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitTypedLocalVariableSyntax(this); + + public override TextSpan Span => TextSpan.Between(this.Name, this.Type); +} \ No newline at end of file diff --git a/src/Bicep.Core/Syntax/TypedVariableBlockSyntax.cs b/src/Bicep.Core/Syntax/TypedVariableBlockSyntax.cs new file mode 100644 index 00000000000..7b6b3a541f0 --- /dev/null +++ b/src/Bicep.Core/Syntax/TypedVariableBlockSyntax.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.Immutable; +using Bicep.Core.Parsing; + +namespace Bicep.Core.Syntax; + +public class TypedVariableBlockSyntax : SyntaxBase +{ + public TypedVariableBlockSyntax(Token openParen, IEnumerable children, SyntaxBase closeParen) + { + AssertTokenType(openParen, nameof(openParen), TokenType.LeftParen); + + this.OpenParen = openParen; + this.Children = children.ToImmutableArray(); + this.CloseParen = closeParen; + this.Arguments = Children.OfType().ToImmutableArray(); + } + + public Token OpenParen { get; } + + public ImmutableArray Children { get; } + + public ImmutableArray Arguments { get; } + + public SyntaxBase CloseParen { get; } + + public override TextSpan Span => TextSpan.Between(this.OpenParen, this.CloseParen); + + public override void Accept(ISyntaxVisitor visitor) => visitor.VisitTypedVariableBlockSyntax(this); +} \ No newline at end of file diff --git a/src/Bicep.Core/Syntax/VariableDeclarationSyntax.cs b/src/Bicep.Core/Syntax/VariableDeclarationSyntax.cs index b3b1ae26360..6169c45143e 100644 --- a/src/Bicep.Core/Syntax/VariableDeclarationSyntax.cs +++ b/src/Bicep.Core/Syntax/VariableDeclarationSyntax.cs @@ -10,20 +10,6 @@ namespace Bicep.Core.Syntax { public class VariableDeclarationSyntax : StatementSyntax, ITopLevelNamedDeclarationSyntax { - public VariableDeclarationSyntax(Token keyword, IdentifierSyntax name, SyntaxBase assignment, SyntaxBase value) - : base(ImmutableArray.Empty) - { - AssertKeyword(keyword, nameof(keyword), LanguageConstants.VariableKeyword); - AssertSyntaxType(name, nameof(name), typeof(IdentifierSyntax)); - AssertSyntaxType(assignment, nameof(assignment), typeof(Token), typeof(SkippedTriviaSyntax)); - AssertTokenType(assignment as Token, nameof(assignment), TokenType.Assignment); - - this.Keyword = keyword; - this.Name = name; - this.Assignment = assignment; - this.Value = value; - } - public VariableDeclarationSyntax(IEnumerable leadingNodes, Token keyword, IdentifierSyntax name, SyntaxBase assignment, SyntaxBase value) : base(leadingNodes) { diff --git a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs index 447624419fc..e07343bd065 100644 --- a/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs +++ b/src/Bicep.Core/TypeSystem/DeclaredTypeManager.cs @@ -153,11 +153,38 @@ public DeclaredTypeManager(TypeManager typeManager, IBinder binder, IFeatureProv case NonNullAssertionSyntax nonNullAssertion: return GetNonNullType(nonNullAssertion); + + case TypedLocalVariableSyntax typedLocalVariable: + return GetTypedLocalVariableType(typedLocalVariable); + + case TypedLambdaSyntax typedLambda: + return GetTypedLambdaType(typedLambda); } return null; } + private DeclaredTypeAssignment GetTypedLocalVariableType(TypedLocalVariableSyntax syntax) + { + var declaredType = TryGetTypeFromTypeSyntax(syntax.Type, allowNamespaceReferences: false) ?? + ErrorType.Create(DiagnosticBuilder.ForPosition(syntax.Type).InvalidParameterType(GetValidTypeNames())); + + return new(declaredType, syntax); + } + + private DeclaredTypeAssignment GetTypedLambdaType(TypedLambdaSyntax syntax) + { + var argumentTypes = syntax.GetLocalVariables() + .Select(x => GetTypedLocalVariableType(x).Reference) + .ToImmutableArray(); + + var returnType = TryGetTypeFromTypeSyntax(syntax.ReturnType, allowNamespaceReferences: false) ?? + ErrorType.Create(DiagnosticBuilder.ForPosition(syntax.ReturnType).InvalidOutputType(GetValidTypeNames())); + + var type = new LambdaType(argumentTypes, returnType); + return new(type, syntax); + } + private DeclaredTypeAssignment GetParameterType(ParameterDeclarationSyntax syntax) { var declaredType = TryGetTypeFromTypeSyntax(syntax.Type, allowNamespaceReferences: false); @@ -405,7 +432,7 @@ private TypeSymbol GetModifiedObject(ObjectType declaredObject, DecorableSyntax private DeclaredTypeAssignment GetOutputType(OutputDeclarationSyntax syntax) { var declaredType = TryGetTypeFromTypeSyntax(syntax.Type, allowNamespaceReferences: false) ?? - ErrorType.Create(DiagnosticBuilder.ForPosition(syntax.Type).InvalidOutputType()); + ErrorType.Create(DiagnosticBuilder.ForPosition(syntax.Type).InvalidOutputType(GetValidTypeNames())); return new(ApplyTypeModifyingDecorators(declaredType.Type, syntax), syntax); } @@ -533,7 +560,7 @@ private TypeSymbol GetObjectTypeType(ObjectTypeSyntax syntax) if (prop.TryGetKeyText() is string propertyName) { - properties.Add(new(propertyName, propertyType, TypePropertyFlags.Required, SemanticModelHelper.TryGetDescription(binder, typeManager.GetDeclaredType, prop))); + properties.Add(new(propertyName, propertyType, TypePropertyFlags.Required, SemanticModelHelper.TryGetDescription(binder, typeManager, prop))); nameBuilder.AppendProperty(propertyName, GetPropertyTypeName(prop.Value, propertyType)); } else { diff --git a/src/Bicep.Core/TypeSystem/DeployTimeConstantContainerVisitor.cs b/src/Bicep.Core/TypeSystem/DeployTimeConstantContainerVisitor.cs index d7693629535..bedf5663ff4 100644 --- a/src/Bicep.Core/TypeSystem/DeployTimeConstantContainerVisitor.cs +++ b/src/Bicep.Core/TypeSystem/DeployTimeConstantContainerVisitor.cs @@ -30,6 +30,13 @@ public static IEnumerable CollectDeployTimeConstantContainers(Semant return visitor.deployTimeConstantContainers; } + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + { + this.deployTimeConstantContainers.Add(syntax); + + base.VisitFunctionDeclarationSyntax(syntax); + } + public override void VisitResourceDeclarationSyntax(ResourceDeclarationSyntax syntax) { if (syntax.IsExistingResource()) diff --git a/src/Bicep.Core/TypeSystem/DeployTimeConstantValidator.cs b/src/Bicep.Core/TypeSystem/DeployTimeConstantValidator.cs index c8904381baf..1c8f2b0432d 100644 --- a/src/Bicep.Core/TypeSystem/DeployTimeConstantValidator.cs +++ b/src/Bicep.Core/TypeSystem/DeployTimeConstantValidator.cs @@ -60,7 +60,8 @@ ForSyntax forSyntax when semanticModel.Binder.GetParent(forSyntax) is VariableDe ForSyntax forSyntax => forSyntax.Expression.AsEnumerable(), FunctionCallSyntaxBase functionCallSyntaxBase => functionCallSyntaxBase.Arguments, - _ => throw new ArgumentOutOfRangeException(nameof(deployTimeConstantContainer), "Expected an ObjectPropertySyntax, a IfConditionSyntax, a ForSyntax, or a FunctionCallSyntaxBase."), + FunctionDeclarationSyntax functionDeclaration => functionDeclaration.AsEnumerable(), + _ => throw new ArgumentOutOfRangeException(nameof(deployTimeConstantContainer), "Expected an ObjectPropertySyntax, an IfConditionSyntax, a ForSyntax, a FunctionCallSyntaxBase, or a FunctionDeclarationSyntax."), }; diff --git a/src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs b/src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs index 2a5d835d1bb..4df2b1d3852 100644 --- a/src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs +++ b/src/Bicep.Core/TypeSystem/DeployTimeConstantViolationVisitor.cs @@ -59,7 +59,8 @@ ForSyntax forSyntax when ErrorSyntaxInForBodyOfVariable(forSyntax, errorSyntax) ForSyntax => diagnosticBuilder.RuntimeValueNotAllowedInForExpression(accessedSymbolName, accessiblePropertyNames, variableDependencyChain), FunctionCallSyntaxBase functionCallSyntaxBase => diagnosticBuilder.RuntimeValueNotAllowedInRunTimeFunctionArguments(functionCallSyntaxBase.Name.IdentifierName, accessedSymbolName, accessiblePropertyNames, variableDependencyChain), - _ => throw new ArgumentOutOfRangeException(nameof(this.DeployTimeConstantContainer), "Expected an ObjectPropertySyntax with a propertyName, a IfConditionSyntax, a ForSyntax, or a FunctionCallSyntaxBase."), + FunctionDeclarationSyntax => diagnosticBuilder.RuntimeValueNotAllowedInFunctionDeclaration(accessedSymbolName, accessiblePropertyNames, variableDependencyChain), + _ => throw new ArgumentOutOfRangeException(nameof(this.DeployTimeConstantContainer), "Expected an ObjectPropertySyntax with a propertyName, an IfConditionSyntax, a ForSyntax, a FunctionCallSyntaxBase, or a FunctionDeclarationSyntax."), }; this.DiagnosticWriter.Write(diagnostic); diff --git a/src/Bicep.Core/TypeSystem/FunctionFlags.cs b/src/Bicep.Core/TypeSystem/FunctionFlags.cs index bfbcfa53486..52d7a1764bc 100644 --- a/src/Bicep.Core/TypeSystem/FunctionFlags.cs +++ b/src/Bicep.Core/TypeSystem/FunctionFlags.cs @@ -71,6 +71,11 @@ public enum FunctionFlags GenerateIntermediateVariableOnIndirectAssignment = 1 << 12, + /// + /// The function can be used as a function decorator. + /// + FunctionDecorator = 1 << 13, + /// /// The function can be used as a resource or module decorator. /// @@ -89,6 +94,6 @@ public enum FunctionFlags /// /// The function can be used as a decorator anywhere. /// - AnyDecorator = ParameterDecorator | VariableDecorator | ResourceDecorator | ModuleDecorator | OutputDecorator | ImportDecorator | MetadataDecorator | TypeDecorator, + AnyDecorator = ParameterDecorator | VariableDecorator | FunctionDecorator | ResourceDecorator | ModuleDecorator | OutputDecorator | ImportDecorator | MetadataDecorator | TypeDecorator, } } diff --git a/src/Bicep.Core/TypeSystem/FunctionResolver.cs b/src/Bicep.Core/TypeSystem/FunctionResolver.cs index 83693178a6c..f73e8fdcd3b 100644 --- a/src/Bicep.Core/TypeSystem/FunctionResolver.cs +++ b/src/Bicep.Core/TypeSystem/FunctionResolver.cs @@ -86,7 +86,7 @@ public ImmutableDictionary GetKnownFunctions() } public static IEnumerable GetMatches( - FunctionSymbol function, + IFunctionSymbol function, IList argumentTypes, out IList argumentCountMismatches, out IList argumentTypeMismatches) diff --git a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs index 3f72171d7f2..a4e92096ea3 100644 --- a/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs +++ b/src/Bicep.Core/TypeSystem/TypeAssignmentVisitor.cs @@ -211,6 +211,17 @@ VariableBlockSyntax block when this.binder.GetParent(block) is LambdaSyntax lamb } }); + public override void VisitTypedLocalVariableSyntax(TypedLocalVariableSyntax syntax) + => AssignType(syntax, () => + { + if (typeManager.GetDeclaredType(syntax) is not {} declaredType) + { + return ErrorType.Empty(); + } + + return declaredType; + }); + public override void VisitForSyntax(ForSyntax syntax) => AssignTypeWithDiagnostics(syntax, diagnostics => { @@ -800,6 +811,24 @@ public override void VisitVariableDeclarationSyntax(VariableDeclarationSyntax sy return valueType; }); + public override void VisitFunctionDeclarationSyntax(FunctionDeclarationSyntax syntax) + => AssignTypeWithDiagnostics(syntax, diagnostics => + { + var errors = new List(); + + var lambdaType = typeManager.GetTypeInfo(syntax.Lambda); + CollectErrors(errors, lambdaType); + + if (PropagateErrorType(errors, lambdaType)) + { + return ErrorType.Create(errors); + } + + this.ValidateDecorators(syntax.Decorators, lambdaType, diagnostics); + + return lambdaType; + }); + public override void VisitOutputDeclarationSyntax(OutputDeclarationSyntax syntax) => AssignTypeWithDiagnostics(syntax, diagnostics => { @@ -1426,6 +1455,9 @@ public override void VisitFunctionCallSyntax(FunctionCallSyntax syntax) case FunctionSymbol function: return GetFunctionSymbolType(function, syntax, errors, diagnostics); + case DeclaredFunctionSymbol declaredFunction: + return GetFunctionSymbolType(declaredFunction, syntax, errors, diagnostics); + case Symbol symbolInfo when binder.NamespaceResolver.GetKnownFunctions(symbolInfo.Name).FirstOrDefault() is {} knownFunction: // A function exists, but it's being shadowed by another symbol in the file return ErrorType.Create( @@ -1447,6 +1479,33 @@ public override void VisitLambdaSyntax(LambdaSyntax syntax) return new LambdaType(argumentTypes.ToImmutableArray(), returnType); }); + public override void VisitTypedLambdaSyntax(TypedLambdaSyntax syntax) + => AssignTypeWithDiagnostics(syntax, diagnostics => + { + var declaredType = typeManager.GetDeclaredType(syntax); + if (declaredType is not LambdaType declaredLambdaType) + { + return declaredType ?? ErrorType.Empty(); + } + + var errors = new List(); + var argumentTypes = new List(); + foreach (var argumentType in declaredLambdaType.ArgumentTypes) + { + CollectErrors(errors, argumentType.Type); + } + + var returnType = TypeValidator.NarrowTypeAndCollectDiagnostics(typeManager, binder, this.parsingErrorLookup, diagnostics, syntax.Body, declaredLambdaType.ReturnType.Type); + CollectErrors(errors, returnType); + + if (PropagateErrorType(errors, argumentTypes)) + { + return ErrorType.Create(errors); + } + + return new LambdaType(declaredLambdaType.ArgumentTypes, returnType); + }); + private Symbol? GetSymbolForDecorator(DecoratorSyntax decorator) { if (binder.GetSymbolInfo(decorator.Expression) is { } symbol) @@ -1691,7 +1750,7 @@ private static void CollectErrors(List errors, ITypeReference r } private TypeSymbol GetFunctionSymbolType( - FunctionSymbol function, + IFunctionSymbol function, FunctionCallSyntaxBase syntax, IList errors, IDiagnosticWriter diagnosticWriter) => GetFunctionSymbolType(function, @@ -1702,7 +1761,7 @@ private TypeSymbol GetFunctionSymbolType( diagnosticWriter); private TypeSymbol GetFunctionSymbolType( - FunctionSymbol function, + IFunctionSymbol function, FunctionCallSyntaxBase syntax, ImmutableArray argumentTypes, IList errors, @@ -1762,7 +1821,7 @@ private TypeSymbol GetFunctionSymbolType( errors.Add(DiagnosticBuilder.ForPosition(syntax.GetArgumentByPosition(argumentIndex)).ArgumentTypeMismatch(argumentType, parameterType)); } } - else + else if (countMismatches.Any()) { // Argument type mismatch wins over count mismatch. Handle count mismatch only when there's no type mismatch. var (actualCount, mininumArgumentCount, maximumArgumentCount) = countMismatches.Aggregate(ArgumentCountMismatch.Reduce); diff --git a/src/Bicep.Core/TypeSystem/TypeValidator.cs b/src/Bicep.Core/TypeSystem/TypeValidator.cs index 8e3b108697d..d1ffb8810b7 100644 --- a/src/Bicep.Core/TypeSystem/TypeValidator.cs +++ b/src/Bicep.Core/TypeSystem/TypeValidator.cs @@ -738,7 +738,7 @@ private TypeSymbol NarrowVariableAccessType(TypeValidatorConfig config, Variable private SyntaxBase? DeclaringSyntax(VariableAccessSyntax variableAccess) => binder.GetSymbolInfo(variableAccess) switch { VariableSymbol variableSymbol => variableSymbol.DeclaringVariable.Value, - LocalVariableSymbol localVariableSymbol => localVariableSymbol.DeclaringLocalVariable, + LocalVariableSymbol localVariableSymbol => localVariableSymbol.DeclaringSyntax, _ => null, }; diff --git a/src/Bicep.Decompiler/TemplateConverter.cs b/src/Bicep.Decompiler/TemplateConverter.cs index 83d956090f9..dc616918028 100644 --- a/src/Bicep.Decompiler/TemplateConverter.cs +++ b/src/Bicep.Decompiler/TemplateConverter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -975,6 +976,7 @@ private VariableDeclarationSyntax ParseVariable(string name, JToken value, bool } return new VariableDeclarationSyntax( + ImmutableArray.Empty, SyntaxFactory.CreateIdentifierToken("var"), SyntaxFactory.CreateIdentifier(identifier), SyntaxFactory.AssignmentToken, diff --git a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index 6e78ac904c0..3e72ebc56b0 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -62,6 +62,8 @@ public class CompletionTests private static readonly SharedLanguageHelperManager ServerWithBuiltInTypes = new(); + private static readonly SharedLanguageHelperManager ServerWithUDFsEnabled = new(); + [NotNull] public TestContext? TestContext { get; set; } @@ -98,6 +100,11 @@ public static void ClassInitialize(TestContext testContext) testContext, services => services.WithFeatureOverrides(new(testContext, UserDefinedTypesEnabled: true)))); + ServerWithUDFsEnabled.Initialize( + async () => await MultiFileLanguageServerHelper.StartLanguageServer( + testContext, + services => services.WithFeatureOverrides(new(testContext, UserDefinedFunctionsEnabled: true)))); + ServerWithBuiltInTypes.Initialize( async () => await MultiFileLanguageServerHelper.StartLanguageServer( testContext, @@ -330,7 +337,7 @@ param myStr string @description('this is a bool value') param myBool bool -param myArray array +param myArray array @description('this is a variable') var myVar = 'foobar' @@ -2265,6 +2272,140 @@ public async Task List_comprehension_functions_return_lambda_snippets_multiple_a "); } + [TestMethod] + public async Task Func_definition_lambda_completions_do_not_suggest_outer_variables() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(""" +var outerVar = 'asdf' + +func foo(innerVar string) string => '${|}' +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + completions.Should().NotContain(x => x.Label == "outerVar"); + var updatedFile = file.ApplyCompletion(completions, "innerVar"); + updatedFile.Should().HaveSourceText(""" +var outerVar = 'asdf' + +func foo(innerVar string) string => '${innerVar|}' +"""); + } + + [TestMethod] + public async Task Func_keyword_completion_is_not_offered_if_experimental_feature_not_enabeld() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(@" +f| +"); + + var file = await new ServerRequestHelper(TestContext, DefaultServer).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + completions.Should().NotContain(x => x.Label == "func"); + } + + [TestMethod] + public async Task Func_keyword_completion_provides_snippet() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(@" +f| +"); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + var updatedFile = file.ApplyCompletion(completions, "func", "foo", "string"); + updatedFile.Should().HaveSourceText(@" +func foo() string => | +"); + } + + [DataTestMethod] + [DataRow("func foo() | => 'blah'", "func foo() string| => 'blah'")] + [DataRow("func foo() a| => 'blah'", "func foo() string| => 'blah'")] + [DataRow("func foo() |a => 'blah'", "func foo() string| => 'blah'")] + [DataRow("func foo() |", "func foo() string|")] + [DataRow("func foo() a|", "func foo() string|")] + [DataRow("func foo() |a", "func foo() string|")] + public async Task Func_lambda_output_type_completions_only_suggest_types(string before, string after) + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor($""" +{before} +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + var updatedFile = file.ApplyCompletion(completions, "string"); + updatedFile.Should().HaveSourceText($""" +{after} +"""); + } + + [DataTestMethod] + [DataRow("func foo(bar |) string => 'blah'", "func foo(bar string|) string => 'blah'")] + [DataRow("func foo(bar a|) string => 'blah'", "func foo(bar string|) string => 'blah'")] + [DataRow("func foo(bar |a) string => 'blah'", "func foo(bar string|) string => 'blah'")] + public async Task Func_lambda_argument_type_completions_only_suggest_types(string before, string after) + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor($""" +{before} +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + var updatedFile = file.ApplyCompletion(completions, "string"); + updatedFile.Should().HaveSourceText($""" +{after} +"""); + } + + [DataTestMethod] + [DataRow("func foo(|) string => 'blah'")] + [DataRow("func foo( | ) string => 'blah'")] + [DataRow("func foo(a|) string => 'blah'")] + [DataRow("func foo(|a) string => 'blah'")] + public async Task Func_lambda_argument_name_offers_no_completions(string before) + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor($""" +{before} +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + completions.Should().BeEmpty(); + } + + [TestMethod] + public async Task Func_usage_completions_are_presented() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(""" +@description('Checks whether the input is true in a roundabout way') +func isTrue(input bool) bool => !(input == false) + +var test = is| +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithUDFsEnabled).OpenFile(text); + + var completions = await file.RequestCompletion(cursor); + + var updatedFile = file.ApplyCompletion(completions, "isTrue"); + updatedFile.Should().HaveSourceText($""" +@description('Checks whether the input is true in a roundabout way') +func isTrue(input bool) bool => !(input == false) + +var test = isTrue(|) +"""); + + var completion = completions.Single(x => x.Label == "isTrue").Documentation!.MarkupContent!.Value + .Should().Contain("Checks whether the input is true in a roundabout way"); + } + [TestMethod] public async Task VerifyCompletionrequestAfterPoundSignWithinComment_ShouldDoNothing() { @@ -3376,7 +3517,7 @@ public async Task LoadFunctionsPathArgument_returnsSymbolsAndFilePathsInCompleti x => x.Label.Should().Be("template3.jsonc"), x => x.Label.Should().Be("template4.json"), x => x.Label.Should().Be("template5.json") - + ); } else diff --git a/src/Bicep.LangServer.IntegrationTests/Helpers/IntegrationTestHelper.cs b/src/Bicep.LangServer.IntegrationTests/Helpers/IntegrationTestHelper.cs index 96c7b859c2d..a09992a3469 100644 --- a/src/Bicep.LangServer.IntegrationTests/Helpers/IntegrationTestHelper.cs +++ b/src/Bicep.LangServer.IntegrationTests/Helpers/IntegrationTestHelper.cs @@ -46,21 +46,14 @@ public static async Task EnsureTaskDoesntCompleteAsync(Task task, int time public static Position GetPosition(ImmutableArray lineStarts, SyntaxBase syntax) { - if (syntax is ISymbolReference reference) - { - // get identifier span otherwise syntax.Span returns the position from the starting position of the whole expression. - // e.g. in an instance function call such as: az.resourceGroup(), syntax.Span position starts at 'az', - // whereas instanceFunctionCall.Name.Span the position will start in resourceGroup() which is what it should be in this - // case. - return PositionHelper.GetPosition(lineStarts, reference.Name.Span.Position); - } - if (syntax is ITopLevelDeclarationSyntax declaration) { return PositionHelper.GetPosition(lineStarts, declaration.Keyword.Span.Position); } - return PositionHelper.GetPosition(lineStarts, syntax.Span.Position); + var name = PositionHelper.GetNameSyntax(syntax); + + return PositionHelper.GetPosition(lineStarts, name.Span.Position); } } } diff --git a/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs b/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs index 2e2e184ac31..1c6c1d87108 100644 --- a/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs +++ b/src/Bicep.LangServer.IntegrationTests/Helpers/ServerRequestHelper.cs @@ -54,6 +54,18 @@ public async Task RequestCompletion(int cursor) }); } + public async Task RequestSignatureHelp(int cursor, SignatureHelpContext? context = null) => + await client.RequestSignatureHelp(new SignatureHelpParams + { + TextDocument = new TextDocumentIdentifier(bicepFile.FileUri), + Position = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor), + Context = context ?? new SignatureHelpContext + { + TriggerKind = SignatureHelpTriggerKind.Invoked, + IsRetrigger = false + } + }); + public BicepFile ApplyCompletion(CompletionList completions, string label, params string[] tabStops) { // Should().Contain is superfluous here, but it gives a better assertion message when it fails @@ -99,6 +111,15 @@ public BicepFile ApplyCompletion(CompletionItem completion, params string[] tabS return SourceFileFactory.CreateBicepFile(bicepFile.FileUri, replaced); } + public async Task RequestHover(int cursor) + { + return await client.RequestHover(new HoverParams + { + TextDocument = new TextDocumentIdentifier(bicepFile.FileUri), + Position = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor) + }); + } + public async Task GotoDefinition(int cursor) { var response = await client.RequestDefinition(new DefinitionParams diff --git a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs index 50bc386ee3b..9533546ce9d 100644 --- a/src/Bicep.LangServer.IntegrationTests/HoverTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/HoverTests.cs @@ -127,14 +127,14 @@ public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers continue; } - switch (symbol!.Kind) + switch (symbol) { - case SymbolKind.Function when symbolReference is VariableAccessSyntax: + case FunctionSymbol when symbolReference is VariableAccessSyntax: // variable got bound to a function hover.Should().BeNull(); break; - case SymbolKind.Error: + case ErrorSymbol: // error symbol hover.Should().BeNull(); break; @@ -553,6 +553,46 @@ param param1 string h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis \nis \nout2\n")); } + [TestMethod] + public async Task Func_usage_hovers_display_information() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(""" +@description('Checks whether the input is true in a roundabout way') +func isTrue(input bool) bool => !(input == false) + +var test = is|True(false) +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); + + var hover = await file.RequestHover(cursor); + hover!.Contents.MarkupContent!.Value.Should().Contain(""" +```bicep +function isTrue(input: bool): bool +``` +"""); + hover!.Contents.MarkupContent!.Value.Should().Contain("Checks whether the input is true in a roundabout way"); + } + + [TestMethod] + public async Task Func_declaration_hovers_display_information() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(""" +@description('Checks whether the input is true in a roundabout way') +func isT|rue(input bool) bool => !(input == false) +"""); + + var file = await new ServerRequestHelper(TestContext, ServerWithBuiltInTypes).OpenFile(text); + + var hover = await file.RequestHover(cursor); + hover!.Contents.MarkupContent!.Value.Should().Contain(""" +```bicep +function isTrue(input: bool): bool +``` +"""); + hover!.Contents.MarkupContent!.Value.Should().Contain("Checks whether the input is true in a roundabout way"); + } + [TestMethod] public async Task PropertyHovers_are_displayed_on_partial_discriminator_objects() { @@ -652,7 +692,7 @@ public async Task ParamHovers_are_displayed_on_param_symbols_params_file() ] ) @description('this is a string value') -param foo string +param foo string @allowed( [ @@ -679,7 +719,7 @@ param foobar bool var (bicepparamText, cursors) = ParserHelper.GetFileWithCursors(bicepparamTextWithCursor,'|'); - + var paramsFile = SourceFileFactory.CreateBicepParamFile(new Uri("file:///path/to/params.bicepparam"), bicepparamText); var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///path/to/main.bicep"), bicepText); @@ -910,6 +950,10 @@ private static void ValidateHover(Hover? hover, Symbol symbol) } break; + case DeclaredFunctionSymbol declaredFunction: + tooltip.Should().Contain($"function {declaredFunction.Name}("); + break; + case LocalVariableSymbol local: tooltip.Should().Contain($"{local.Name}: {local.Type}"); break; diff --git a/src/Bicep.LangServer.IntegrationTests/RenameSymbolTests.cs b/src/Bicep.LangServer.IntegrationTests/RenameSymbolTests.cs index c08e20f957c..8643afbabe8 100644 --- a/src/Bicep.LangServer.IntegrationTests/RenameSymbolTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/RenameSymbolTests.cs @@ -110,7 +110,7 @@ public async Task RenamingFunctionsShouldProduceEmptyEdit(DataSet dataSet) var lineStarts = compilation.SourceFileGrouping.EntryPoint.LineStarts; var validFunctionCallPairs = symbolTable - .Where(pair => pair.Value.Kind == SymbolKind.Function) + .Where(pair => pair.Value is FunctionSymbol) .Select(pair => pair.Key); foreach (var syntax in validFunctionCallPairs) diff --git a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs index 57a2e248b0f..12be7b1033e 100644 --- a/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/SignatureHelpTests.cs @@ -11,7 +11,9 @@ using Bicep.Core.Semantics; using Bicep.Core.Syntax; using Bicep.Core.Syntax.Visitors; +using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Utils; using Bicep.Core.Workspaces; using Bicep.LangServer.IntegrationTests.Extensions; using Bicep.LangServer.IntegrationTests.Helpers; @@ -82,12 +84,12 @@ public async Task ShouldProvideSignatureHelpBetweenFunctionParentheses(DataSet d // if the cursor is present immediate after the function argument opening paren, // the signature help can only show the signature of the enclosing function var startOffset = functionCall.OpenParen.GetEndPosition(); - await ValidateOffset(helper.Client, uri, tree, startOffset, symbol as FunctionSymbol, expectDecorator); + await ValidateOffset(helper.Client, uri, tree, startOffset, symbol as IFunctionSymbol, expectDecorator); // if the cursor is present immediately before the function argument closing paren, // the signature help can only show the signature of the enclosing function var endOffset = functionCall.CloseParen.Span.Position; - await ValidateOffset(helper.Client, uri, tree, endOffset, symbol as FunctionSymbol, expectDecorator); + await ValidateOffset(helper.Client, uri, tree, endOffset, symbol as IFunctionSymbol, expectDecorator); } } @@ -141,7 +143,27 @@ public async Task NonFunctionCallSyntaxShouldProvideNoSignatureHelp(DataSet data } } - private static async Task ValidateOffset(ILanguageClient client, DocumentUri uri, BicepSourceFile bicepFile, int offset, FunctionSymbol? symbol, bool expectDecorator) + [TestMethod] + public async Task Signature_help_works_with_user_defined_functions() + { + var (text, cursor) = ParserHelper.GetFileWithSingleCursor(@" +@description('Checks whether the input is true in a roundabout way') +func isTrue(input bool) bool => !(input == false) + +var test = isTrue(|) +"); + + using var server = await MultiFileLanguageServerHelper.StartLanguageServer(TestContext, services => services.WithFeatureOverrides(new(UserDefinedFunctionsEnabled: true))); + var file = await new ServerRequestHelper(TestContext, server).OpenFile(text); + + var signatureHelp = await file.RequestSignatureHelp(cursor); + var signature = signatureHelp!.Signatures.Single(); + + signature.Label.Should().Be("isTrue(input: bool): bool"); + signature.Documentation!.MarkupContent!.Value.Should().Be("Checks whether the input is true in a roundabout way"); + } + + private static async Task ValidateOffset(ILanguageClient client, DocumentUri uri, BicepSourceFile bicepFile, int offset, IFunctionSymbol? symbol, bool expectDecorator) { var position = PositionHelper.GetPosition(bicepFile.LineStarts, offset); var initial = await RequestSignatureHelp(client, position, uri); @@ -184,7 +206,7 @@ private static async Task ValidateOffset(ILanguageClient client, DocumentUri uri } } - private static void AssertValidSignatureHelp(SignatureHelp? signatureHelp, FunctionSymbol symbol, bool expectDecorator) + private static void AssertValidSignatureHelp(SignatureHelp? signatureHelp, IFunctionSymbol symbol, bool expectDecorator) { signatureHelp.Should().NotBeNull(); @@ -227,8 +249,9 @@ private static void AssertValidSignatureHelp(SignatureHelp? signatureHelp, Funct signature.Documentation!.MarkupContent.Should().NotBeNull(); signature.Documentation.MarkupContent!.Kind.Should().Be(MarkupKind.Markdown); + // func declarations will only contain documentation if there's a @description decorator. // List functions provided by the bicep-types-az library do not contain documentation: https://github.com/Azure/bicep/issues/7611 - if (!isWellKnownListFunction) + if (symbol is not DeclaredFunctionSymbol && !isWellKnownListFunction) { signature.Documentation.MarkupContent.Value.Should().NotBeEmpty(); } diff --git a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs index 561efd1d56e..2c9ba8297cf 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionContext.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionContext.cs @@ -195,7 +195,9 @@ public static BicepCompletionContext Create(IFeatureProvider featureProvider, Co ConvertFlag(IsParameterIdentifierContext(matchingNodes, offset), BicepCompletionContextKind.ParamIdentifier) | ConvertFlag(IsParameterValueContext(matchingNodes, offset), BicepCompletionContextKind.ParamValue) | ConvertFlag(IsObjectTypePropertyValueContext(matchingNodes, offset), BicepCompletionContextKind.ObjectTypePropertyValue) | - ConvertFlag(IsUnionTypeMemberContext(matchingNodes, offset), BicepCompletionContextKind.UnionTypeMember); + ConvertFlag(IsUnionTypeMemberContext(matchingNodes, offset), BicepCompletionContextKind.UnionTypeMember) | + ConvertFlag(IsTypedLocalVariableTypeContext(matchingNodes, offset), BicepCompletionContextKind.TypedLocalVariableType) | + ConvertFlag(IsTypedLambdaOutputTypeContext(matchingNodes, offset), BicepCompletionContextKind.TypedLambdaOutputType); if (featureProvider.ExtensibilityEnabled) { @@ -992,6 +994,20 @@ TokenType.StringMiddlePiece when IsOffsetImmediatlyAfterNode(offset, token) => f matchingNodes, (arraySyntax, token) => token.Type != TokenType.NewLine || !CanInsertChildNodeAtOffset(arraySyntax, offset)) || + // var test = map([], ( | ) => 'asdf') + SyntaxMatcher.IsTailMatch(matchingNodes) || + // var test = map([], (a|) => 'asdf') + SyntaxMatcher.IsTailMatch(matchingNodes) || + // var test = map([], (|) => 'asdf') + SyntaxMatcher.IsTailMatch(matchingNodes) || + + // func foo( | ) string => 'asdf' + SyntaxMatcher.IsTailMatch(matchingNodes) || + // func foo(a|) string => 'asdf' + SyntaxMatcher.IsTailMatch(matchingNodes) || + // func foo(|) string => 'asdf' + SyntaxMatcher.IsTailMatch(matchingNodes) || + // var foo = ! | bar SyntaxMatcher.IsTailMatch( matchingNodes, @@ -1073,6 +1089,18 @@ private static bool IsParameterValueContext(List matchingNodes, int // param foo = 'o|' SyntaxMatcher.IsTailMatch(matchingNodes, (_, _, token) => token.Type == TokenType.StringComplete); + private static bool IsTypedLambdaOutputTypeContext(List matchingNodes, int offset) => + // func foo() | => bar + SyntaxMatcher.IsTailMatch(matchingNodes, (lambda) => offset > lambda.VariableSection.GetEndPosition() && (lambda.Arrow.IsSkipped || offset < lambda.Arrow.GetPosition())) || + // func foo() a| => bar + SyntaxMatcher.IsTailMatch(matchingNodes, (lambda, variable, _, _) => lambda.ReturnType == variable); + + private static bool IsTypedLocalVariableTypeContext(List matchingNodes, int offset) => + // func foo(a |) string => bar + SyntaxMatcher.IsTailMatch(matchingNodes, (_, variable) => offset > variable.Name.GetEndPosition()) || + // func foo(a b|) string => bar + SyntaxMatcher.IsTailMatch(matchingNodes, (_, variable, type, _, _) => variable.Type == type); + private static bool IsResourceDependsOnArrayItemContext(BicepCompletionContextKind kind, string? propertyName, SyntaxBase? topLevelDeclarationInfo) { return propertyName == LanguageConstants.ResourceDependsOnPropertyName diff --git a/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs b/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs index 511274af1e2..e17cbb0cb9e 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionContextKind.cs @@ -204,6 +204,16 @@ public enum BicepCompletionContextKind : ulong /// /// The current location can accept a symbolic reference to a resource. /// - ExpectsResourceSymbolicReference = 1UL << 37 + ExpectsResourceSymbolicReference = 1UL << 37, + + /// + /// Cursor is on a typed lambda argument type. + /// + TypedLocalVariableType = 1UL << 38, + + /// + /// Cursor is on a typed lambda output type. + /// + TypedLambdaOutputType = 1UL << 39, } } diff --git a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs index 20d08485f79..3bb9137bf83 100644 --- a/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs +++ b/src/Bicep.LangServer/Completions/BicepCompletionProvider.cs @@ -174,6 +174,15 @@ private IEnumerable GetDeclarationCompletions(SemanticModel mode yield return CreateKeywordCompletion(LanguageConstants.ImportKeyword, "Import keyword", context.ReplacementRange); } + if (model.Features.UserDefinedFunctionsEnabled) + { + yield return CreateContextualSnippetCompletion( + LanguageConstants.FunctionKeyword, + "Function declaration", + "func ${1:name}() ${2:outputType} => $0", + context.ReplacementRange); + } + foreach (Snippet resourceSnippet in SnippetsProvider.GetTopLevelNamedDeclarationSnippets()) { string prefix = resourceSnippet.Prefix; @@ -313,6 +322,13 @@ private IEnumerable GetDeclarationTypeCompletions(SemanticModel return GetUserDefinedTypeCompletions(model, context, declared => !ReferenceEquals(declared.DeclaringType, cyclableType) && IsTypeLiteralSyntax(declared.DeclaringType.Value)); } + if (context.Kind.HasFlag(BicepCompletionContextKind.TypedLocalVariableType) || + context.Kind.HasFlag(BicepCompletionContextKind.TypedLambdaOutputType)) + { + // user-defined functions don't yet support user-defined types + return GetAmbientTypeCompletions(model, context); + } + if (context.Kind.HasFlag(BicepCompletionContextKind.OutputType)) { var completions = GetAmbientTypeCompletions(model, context); @@ -966,11 +982,16 @@ IEnumerable GetAccessibleDecoratorFunctionsWithCache(NamespaceTy // add accessible symbols from innermost scope and then move to outer scopes // reverse loop iteration - for (int depth = context.ActiveScopes.Length - 1; depth >= 0; depth--) + foreach (var scope in context.ActiveScopes.Reverse()) { // add the non-output declarations with valid identifiers at current scope - var currentScope = context.ActiveScopes[depth]; - AddSymbolCompletions(completions, currentScope.Declarations.Where(decl => decl.NameSource.IsValid && !(decl is OutputSymbol))); + AddSymbolCompletions(completions, scope.Declarations.Where(decl => decl.NameSource.IsValid && decl is not OutputSymbol)); + + if (scope.ScopeResolution == ScopeResolution.GlobalsOnly) + { + // don't inherit outer scope variables + break; + } } } else @@ -1042,6 +1063,7 @@ IEnumerable GetAccessible(IEnumerable symbols, T ParameterSymbol parameterSymbol => GetAccessible(knownDecoratorFunctions, parameterSymbol.Type, FunctionFlags.ParameterDecorator), TypeAliasSymbol declaredTypeSymbol => GetAccessible(knownDecoratorFunctions, declaredTypeSymbol.UnwrapType(), FunctionFlags.TypeDecorator), VariableSymbol variableSymbol => GetAccessible(knownDecoratorFunctions, variableSymbol.Type, FunctionFlags.VariableDecorator), + DeclaredFunctionSymbol functionSymbol => GetAccessible(knownDecoratorFunctions, functionSymbol.Type, FunctionFlags.FunctionDecorator), ResourceSymbol resourceSymbol => GetAccessible(knownDecoratorFunctions, resourceSymbol.Type, FunctionFlags.ResourceDecorator), ModuleSymbol moduleSymbol => GetAccessible(knownDecoratorFunctions, moduleSymbol.Type, FunctionFlags.ModuleDecorator), OutputSymbol outputSymbol => GetAccessible(knownDecoratorFunctions, outputSymbol.Type, FunctionFlags.OutputDecorator), @@ -1212,7 +1234,7 @@ private IEnumerable GetFunctionParamCompletions(SemanticModel mo { if (!context.Kind.HasFlag(BicepCompletionContextKind.FunctionArgument) || context.FunctionArgument is not { } functionArgument - || model.GetSymbolInfo(functionArgument.Function) is not FunctionSymbol functionSymbol) + || model.GetSymbolInfo(functionArgument.Function) is not IFunctionSymbol functionSymbol) { return Enumerable.Empty(); } @@ -1725,7 +1747,7 @@ private static CompletionItem CreateSymbolCompletion(Symbol symbol, Range replac completion.WithCommitCharacters(ResourceSymbolCommitChars); } - if (symbol is FunctionSymbol function) + if (symbol is IFunctionSymbol function) { // for functions without any parameters on all the overloads, we should be placing the cursor after the parentheses // for all other functions, the cursor should land between the parentheses so the user can specify the arguments @@ -1896,13 +1918,13 @@ private static string FormatPropertyDocumentation(TypeProperty property) } private static string? TryGetSymbolDocumentationMarkdown(Symbol symbol, SemanticModel model) - { + { if(symbol is DeclaredSymbol declaredSymbol && declaredSymbol.DeclaringSyntax is DecorableSyntax decorableSyntax) { var documentation = SemanticModelHelper.TryGetDescription(model, decorableSyntax); if(declaredSymbol is ParameterSymbol) { - documentation = $"Type: {declaredSymbol.Type}" + (documentation is null ? "" : $"{MarkdownNewLine}{documentation}"); + documentation = $"Type: {declaredSymbol.Type}" + (documentation is null ? "" : $"{MarkdownNewLine}{documentation}"); } return documentation; } diff --git a/src/Bicep.LangServer/Completions/SyntaxMatcher.cs b/src/Bicep.LangServer/Completions/SyntaxMatcher.cs index 42f3f05a02f..314605cbda3 100644 --- a/src/Bicep.LangServer/Completions/SyntaxMatcher.cs +++ b/src/Bicep.LangServer/Completions/SyntaxMatcher.cs @@ -65,7 +65,7 @@ public static bool IsTailMatch(IList nodes, Func(IList nodes, Func predicate, Action? actionOnMatch = null) + public static bool IsTailMatch(IList nodes, Func? predicate = null, Action? actionOnMatch = null) where T1 : SyntaxBase where T2 : SyntaxBase where T3 : SyntaxBase @@ -76,7 +76,7 @@ public static bool IsTailMatch(IList nodes, Func SymbolKind.Field, TypeAliasSymbol => SymbolKind.Field, VariableSymbol => SymbolKind.Variable, + DeclaredFunctionSymbol => SymbolKind.Function, ResourceSymbol => SymbolKind.Object, ModuleSymbol => SymbolKind.Module, OutputSymbol => SymbolKind.Interface, @@ -94,6 +95,7 @@ private DocumentSymbol CreateDocumentSymbol(SemanticModel model, DeclaredSymbol ParameterSymbol parameter => parameter.Type.Name, TypeAliasSymbol declaredType => declaredType.Type.Name, VariableSymbol variable => variable.Type.Name, + DeclaredFunctionSymbol func => func.Type.Name, ResourceSymbol resource => resource.Type.Name, ModuleSymbol module => module.Type.Name, OutputSymbol output => output.Type.Name, diff --git a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs index 25dedf98422..dca8c56e41d 100644 --- a/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepHoverHandler.cs @@ -148,11 +148,14 @@ public BicepHoverHandler( case BuiltInNamespaceSymbol builtInNamespace: return WithMarkdown(CodeBlock($"{builtInNamespace.Name} namespace")); - case FunctionSymbol function when result.Origin is FunctionCallSyntaxBase functionCall: + case IFunctionSymbol function when result.Origin is FunctionCallSyntaxBase functionCall: // it's not possible for a non-function call syntax to resolve to a function symbol // but this simplifies the checks return GetFunctionMarkdown(function, functionCall, result.Context.Compilation.GetEntrypointSemanticModel()); + case DeclaredFunctionSymbol function: + // A declared function can only have a single overload! + return WithMarkdown(GetFunctionOverloadMarkdown(function.Overloads.Single())); case PropertySymbol property: return WithMarkdown(CodeBlockWithDescription($"{property.Name}: {property.Type}", property.Description)); @@ -255,11 +258,11 @@ private static string CodeBlock(string content) => // Markdown needs two leading whitespaces before newline to insert a line break private static string CodeBlockWithDescription(string content, string? description) => CodeBlock(content) + (description is not null ? $"{description.Replace("\n", " \n")}\n" : string.Empty); - private static MarkedStringsOrMarkupContent GetFunctionMarkdown(FunctionSymbol function, FunctionCallSyntaxBase functionCall, SemanticModel model) + private static MarkedStringsOrMarkupContent GetFunctionMarkdown(IFunctionSymbol function, FunctionCallSyntaxBase functionCall, SemanticModel model) { if (model.TypeManager.GetMatchedFunctionOverload(functionCall) is { } matchedOverload) { - return WithMarkdown(GetFunctionOverloadMarkdown(matchedOverload, function.Overloads.Length - 1)); + return WithMarkdown(GetFunctionOverloadMarkdown(matchedOverload)); } var potentialMatches = @@ -276,7 +279,7 @@ private static MarkedStringsOrMarkupContent GetFunctionMarkdown(FunctionSymbol f return WithMarkdown(toShow.Select(GetFunctionOverloadMarkdown)); } - private static string GetFunctionOverloadMarkdown(FunctionOverload overload, int functionOverloadCount) + private static string GetFunctionOverloadMarkdown(FunctionOverload overload) => CodeBlockWithDescription($"function {overload.Name}{overload.TypeSignature}", overload.Description); private static string? TryGetTypeDocumentationLink(ResourceSymbol resource) diff --git a/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs b/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs index 51a26bb7e2a..3950164938c 100644 --- a/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs +++ b/src/Bicep.LangServer/Handlers/BicepSignatureHelpHandler.cs @@ -56,7 +56,7 @@ public BicepSignatureHelpHandler(ICompilationManager compilationManager) var semanticModel = context.Compilation.GetEntrypointSemanticModel(); var symbol = semanticModel.GetSymbolInfo(functionCall); - if (symbol is not FunctionSymbol functionSymbol) + if (symbol is not IFunctionSymbol functionSymbol) { // no symbol or symbol is not a function return NoHelp(); @@ -149,7 +149,7 @@ private static List NormalizeArgumentTypes(ImmutableArray arguments, List normalizedArgumentTypes, FunctionSymbol symbol, int offset, bool includeReturnType) + private static SignatureHelp CreateSignatureHelp(ImmutableArray arguments, List normalizedArgumentTypes, IFunctionSymbol symbol, int offset, bool includeReturnType) { // exclude overloads where the specified arguments have exceeded the maximum // allow count mismatches because the user may not have started typing the arguments yet diff --git a/src/highlightjs/src/bicep.ts b/src/highlightjs/src/bicep.ts index 726c1cb0482..1f9019ae597 100644 --- a/src/highlightjs/src/bicep.ts +++ b/src/highlightjs/src/bicep.ts @@ -36,6 +36,7 @@ const KEYWORDS = { 'type', 'with', 'using', + 'func', ], literal: [ "true", diff --git a/src/highlightjs/test/baselines/basic.html b/src/highlightjs/test/baselines/basic.html index c3b94420f16..5a00d69473b 100644 --- a/src/highlightjs/test/baselines/basic.html +++ b/src/highlightjs/test/baselines/basic.html @@ -75,8 +75,8 @@ hello! ''' -var func = resourceGroup().location -var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) +var func = resourceGroup().location +var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) var func3 = union({ 'abc': resourceGroup().id }, { diff --git a/src/highlightjs/test/baselines/bicepconfig.json b/src/highlightjs/test/baselines/bicepconfig.json index c2509706a5a..72f8b60641d 100644 --- a/src/highlightjs/test/baselines/bicepconfig.json +++ b/src/highlightjs/test/baselines/bicepconfig.json @@ -1,5 +1,6 @@ { "experimentalFeaturesEnabled": { + "userDefinedFunctions": true, "userDefinedTypes": true, "paramsFiles": true } diff --git a/src/highlightjs/test/baselines/functions.bicep b/src/highlightjs/test/baselines/functions.bicep new file mode 100644 index 00000000000..3897b77af7d --- /dev/null +++ b/src/highlightjs/test/baselines/functions.bicep @@ -0,0 +1,15 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] diff --git a/src/highlightjs/test/baselines/functions.html b/src/highlightjs/test/baselines/functions.html new file mode 100644 index 00000000000..0ec71a793fa --- /dev/null +++ b/src/highlightjs/test/baselines/functions.html @@ -0,0 +1,30 @@ + + + + + + + +
+func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}'
+
+output foo string = buildUrl(true, 'google.com', 'search')
+
+func sayHello(name string) string => 'Hi ${name}!'
+
+output hellos array = map(['Evie', 'Casper'], name => sayHello(name))
+
+func objReturnType(name string) object => {
+  hello: 'Hi ${name}!'
+}
+
+func arrayReturnType(name string) array => [
+  name
+]
+
+    
+ + \ No newline at end of file diff --git a/src/monarch/src/bicep.ts b/src/monarch/src/bicep.ts index a11a3cfc315..1294335d331 100644 --- a/src/monarch/src/bicep.ts +++ b/src/monarch/src/bicep.ts @@ -27,6 +27,7 @@ const keywords = [ 'type', 'with', 'using', + 'func', ]; const namedLiterals = [ diff --git a/src/monarch/test/baselines/basic.html b/src/monarch/test/baselines/basic.html index d3335c133e9..01beec66eba 100644 --- a/src/monarch/test/baselines/basic.html +++ b/src/monarch/test/baselines/basic.html @@ -75,8 +75,8 @@ hello! ''' -var func = resourceGroup().location -var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) +var func = resourceGroup().location +var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) var func3 = union({ 'abc': resourceGroup().id }, { diff --git a/src/monarch/test/baselines/bicepconfig.json b/src/monarch/test/baselines/bicepconfig.json index c2509706a5a..72f8b60641d 100644 --- a/src/monarch/test/baselines/bicepconfig.json +++ b/src/monarch/test/baselines/bicepconfig.json @@ -1,5 +1,6 @@ { "experimentalFeaturesEnabled": { + "userDefinedFunctions": true, "userDefinedTypes": true, "paramsFiles": true } diff --git a/src/monarch/test/baselines/functions.bicep b/src/monarch/test/baselines/functions.bicep new file mode 100644 index 00000000000..3897b77af7d --- /dev/null +++ b/src/monarch/test/baselines/functions.bicep @@ -0,0 +1,15 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] diff --git a/src/monarch/test/baselines/functions.html b/src/monarch/test/baselines/functions.html new file mode 100644 index 00000000000..2950906898f --- /dev/null +++ b/src/monarch/test/baselines/functions.html @@ -0,0 +1,31 @@ + + + + + + + +
+func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}'
+
+output foo string = buildUrl(true, 'google.com', 'search')
+
+func sayHello(name string) string => 'Hi ${name}!'
+
+output hellos array = map(['Evie', 'Casper'], name => sayHello(name))
+
+func objReturnType(name string) object => {
+  hello: 'Hi ${name}!'
+}
+
+func arrayReturnType(name string) array => [
+  name
+]
+
+
+    
+ + \ No newline at end of file diff --git a/src/textmate/bicep.tmlanguage b/src/textmate/bicep.tmlanguage index ea3da175f09..3f710791d90 100644 --- a/src/textmate/bicep.tmlanguage +++ b/src/textmate/bicep.tmlanguage @@ -216,7 +216,7 @@ name keyword.control.declaration.bicep match - \b(metadata|targetScope|resource|module|param|var|output|for|in|if|existing|import|as|type|with|using)\b + \b(metadata|targetScope|resource|module|param|var|output|for|in|if|existing|import|as|type|with|using|func)\b lambda-start diff --git a/src/textmate/src/bicep.ts b/src/textmate/src/bicep.ts index f063cde6423..b61453e0e1a 100644 --- a/src/textmate/src/bicep.ts +++ b/src/textmate/src/bicep.ts @@ -61,6 +61,7 @@ const keywords = [ 'type', 'with', 'using', + 'func', ]; const keywordExpression: MatchRule = { diff --git a/src/textmate/test/baselines/basic.html b/src/textmate/test/baselines/basic.html index cc5f96eb487..2de8a843343 100644 --- a/src/textmate/test/baselines/basic.html +++ b/src/textmate/test/baselines/basic.html @@ -75,8 +75,8 @@ hello! ''' -var func = resourceGroup().location -var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) +var func = resourceGroup().location +var func2 = reference('Microsoft.KeyVault/vaults/secrets', func) var func3 = union({ 'abc': resourceGroup().id }, { diff --git a/src/textmate/test/baselines/bicepconfig.json b/src/textmate/test/baselines/bicepconfig.json index c2509706a5a..72f8b60641d 100644 --- a/src/textmate/test/baselines/bicepconfig.json +++ b/src/textmate/test/baselines/bicepconfig.json @@ -1,5 +1,6 @@ { "experimentalFeaturesEnabled": { + "userDefinedFunctions": true, "userDefinedTypes": true, "paramsFiles": true } diff --git a/src/textmate/test/baselines/functions.bicep b/src/textmate/test/baselines/functions.bicep new file mode 100644 index 00000000000..3897b77af7d --- /dev/null +++ b/src/textmate/test/baselines/functions.bicep @@ -0,0 +1,15 @@ +func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}' + +output foo string = buildUrl(true, 'google.com', 'search') + +func sayHello(name string) string => 'Hi ${name}!' + +output hellos array = map(['Evie', 'Casper'], name => sayHello(name)) + +func objReturnType(name string) object => { + hello: 'Hi ${name}!' +} + +func arrayReturnType(name string) array => [ + name +] diff --git a/src/textmate/test/baselines/functions.html b/src/textmate/test/baselines/functions.html new file mode 100644 index 00000000000..3b16a848458 --- /dev/null +++ b/src/textmate/test/baselines/functions.html @@ -0,0 +1,31 @@ + + + + + + + +
+func buildUrl(https bool, hostname string, path string) string => '${https ? 'https' : 'http'}://${hostname}${empty(path) ? '' : '/${path}'}'
+
+output foo string = buildUrl(true, 'google.com', 'search')
+
+func sayHello(name string) string => 'Hi ${name}!'
+
+output hellos array = map(['Evie', 'Casper'], name => sayHello(name))
+
+func objReturnType(name string) object => {
+  hello: 'Hi ${name}!'
+}
+
+func arrayReturnType(name string) array => [
+  name
+]
+
+
+    
+ + \ No newline at end of file diff --git a/src/vscode-bicep/schemas/bicepconfig.schema.json b/src/vscode-bicep/schemas/bicepconfig.schema.json index 84c102783ec..eff18e14f94 100644 --- a/src/vscode-bicep/schemas/bicepconfig.schema.json +++ b/src/vscode-bicep/schemas/bicepconfig.schema.json @@ -625,6 +625,9 @@ }, "userDefinedTypes": { "type": "boolean" + }, + "userDefinedFunctions": { + "type": "boolean" } } }