Skip to content

Commit

Permalink
Add experimental support for aggregate type declarations (Azure#8673)
Browse files Browse the repository at this point in the history
* Add feature flag for aggregate types

* Add new syntax classes for type expressions

* Add type expression parsing to TypeManager

* Add aggregate type support to TemplateWriter

* Add types samples

* Improve diagnostic reporting by deferring declared type resolution only when absolutely necessary

* Make $ref writing purely syntax driven

* Fixup integer and boolean type literal support

* Make sure decorators used on types are valid type decorators

* Run baselines

* Fixup tests

* Cleanup parameter type assignment when @Allowed decorator used

* Require cursor-based tests to specify the character that represents a cursor position

* Fixup LangServer tests

* Add rudimentary completions, hovers, and semantic highlighting

* Improve cycle detection and reporting for objects

* Disallow newlines between union members for now

Since there's no terminating character, an incomplete union (`'foo'|`) will treat the start of the next declaration as skipped trivia unless inner union newlines are blocked

* Add hover tests

* Add type keyword to Textmate grammar

* fixup some todos and typos left in PR

* Check properties of param default values

* Better completions for union types

* Show warning when an object with additional properties only accepts them as a fallback

* Raise an error diagnostic on user-defined types whose name masks an ambient type

* Update diagnostic messages for clarity

* Update Highlight.js and Monarch grammars + baselines

* Rename types vnext feature to 'UserDefinedTypes'

* Update grammar to include user-defined types

* Add types flag to bicepconfig schema

* Rename DeclaredTypeSymbol to TypeAliasSymbol

* Block types named 'resource' and update associated diagnostic message

* Check for nested type errors in TypeAssignmentVisitor, not in SemanticValidationVisitor

* Remove recursion guard from SymbolVisitor

* Cleanup and consolidate per PR feedback

* Fix test
  • Loading branch information
jeskew authored Oct 30, 2022
1 parent 76af604 commit 3f448ab
Show file tree
Hide file tree
Showing 121 changed files with 5,921 additions and 671 deletions.
56 changes: 41 additions & 15 deletions docs/grammar.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Language Grammar
The following is the active pseudo-grammar of the bicep language.
```
program -> statement* EOF
statement ->
targetScopeDecl |
importDecl |
program -> statement* EOF
statement ->
targetScopeDecl |
importDecl |
metadataDecl |
parameterDecl |
parameterDecl |
typeDecl |
variableDecl |
resourceDecl |
moduleDecl |
Expand All @@ -19,18 +20,20 @@ importDecl -> decorator* "import" IDENTIFIER(providerName) "as" IDENTIFIER(alias
metadataDecl -> "metadata" IDENTIFIER(name) "=" expression NL
parameterDecl ->
decorator* "parameter" IDENTIFIER(name) IDENTIFIER(type) parameterDefaultValue? NL |
parameterDecl ->
decorator* "parameter" IDENTIFIER(name) typeExpression parameterDefaultValue? NL |
decorator* "parameter" IDENTIFIER(name) "resource" interpString(type) parameterDefaultValue? NL |
parameterDefaultValue -> "=" expression
typeDecl -> decorator* "type" IDENTIFIER(name) "=" typeExpression NL
variableDecl -> decorator* "variable" IDENTIFIER(name) "=" expression NL
resourceDecl -> decorator* "resource" IDENTIFIER(name) interpString(type) "existing"? "=" (ifCondition | object | forExpression) NL
moduleDecl -> decorator* "module" IDENTIFIER(name) interpString(type) "=" (ifCondition | object | forExpression) NL
outputDecl ->
outputDecl ->
decorator* "output" IDENTIFIER(name) IDENTIFIER(type) "=" expression NL
decorator* "output" IDENTIFIER(name) "resource" interpString(type) "=" expression NL
NL -> ("\n" | "\r")+
Expand All @@ -39,34 +42,34 @@ decorator -> "@" decoratorExpression NL
disableNextLineDiagnosticsDirective-> #disable-next-line diagnosticCode1 diagnosticCode2 diagnosticCode3 NL
expression ->
expression ->
binaryExpression |
binaryExpression "?" expression ":" expression
binaryExpression ->
binaryExpression ->
equalityExpression |
binaryExpression "&&" equalityExpression |
binaryExpression "||" equalityExpression |
binaryExpression "??" equalityExpression
equalityExpression ->
equalityExpression ->
relationalExpression |
equalityExpression "==" relationalExpression |
equalityExpression "!=" relationalExpression
relationalExpression ->
relationalExpression ->
additiveExpression |
relationalExpression ">" additiveExpression |
relationalExpression ">=" additiveExpression |
relationalExpression "<" additiveExpression |
relationalExpression "<=" additiveExpression
additiveExpression ->
additiveExpression ->
multiplicativeExpression |
additiveExpression "+" multiplicativeExpression |
additiveExpression "-" multiplicativeExpression
multiplicativeExpression ->
multiplicativeExpression ->
unaryExpression |
multiplicativeExpression "*" unaryExpression |
multiplicativeExpression "/" unaryExpression |
Expand Down Expand Up @@ -123,9 +126,32 @@ multilineString -> "'''" + MULTILINESTRINGCHAR+ + "'''"
literalValue -> NUMBER | "true" | "false" | "null"
object -> "{" ( NL+ ( objectProperty NL+ )* )? "}"
objectProperty -> ( IDENTIFIER(name) | interpString ) ":" expression
objectProperty -> ( IDENTIFIER(name) | interpString ) ":" expression
array -> "[" ( NL+ arrayItem* )? "]"
arrayItem -> expression NL+
typeExpression -> singularTypeExpression ("|" singularTypeExpression)*
singularTypeExpression ->
primaryTypeExpression |
singularTypeExpression "[]" |
parenthesizedTypeExpression
primaryTypeExpression ->
ambientTypeReference |
IDENTIFIER(type) |
literalValue |
unaryOperator literalValue |
stringComplete |
multilineString |
objectType
ambientTypeReference -> "string" | "int" | "bool" | "array" | "object"
objectType -> "{" (NL+ (objectTypeProperty NL+ )* )? "}"
objectTypeProperty -> decorator* ( IDENTIFIER(name) | stringComplete | multilineString ) ":" typeExpression
parenthesizedTypeExpression -> "(" typeExpression ")"
```
24 changes: 16 additions & 8 deletions src/Bicep.Core.IntegrationTests/LambdaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ public void Map_function_preserves_types_accurately_integers()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = map([123], ab|c => abc)
var fo|o2 = map([123], a|bc => 'Hello ${abc}')
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand All @@ -108,7 +109,8 @@ public void Map_function_preserves_types_accurately_strings()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = map(['abc', 'def'], ab|c => abc)
var fo|o2 = map(['abc', 'def'], a|bc => length(abc))
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand All @@ -127,7 +129,8 @@ public void Map_function_works_with_unknowable_types()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = map([], ab|c => abc)
var fo|o2 = map([any('foo')], a|bc => 'Hi ${abc}!')
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand All @@ -146,7 +149,8 @@ public void Map_function_blocks_incorrect_args()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var foo = map([123], (abc, def) => abc)
var foo2 = map(['foo'], () => 'Hi!')
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[] {
Expand Down Expand Up @@ -185,7 +189,8 @@ public void Filter_lambda_functions_assigns_types_accurately()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = filter([123], abc => a|bc == 123)
var fo|o2 = filter(['abc', 'def'], a|bc => abc == '123')
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand All @@ -204,7 +209,8 @@ public void Sort_lambda_functions_assigns_types_accurately()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = sort([123], (abc, def) => a|bc < def)
var fo|o2 = sort(['bar', 'foo'], (abc, def) => abc < d|ef)
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand All @@ -223,7 +229,8 @@ public void Reduce_lambda_functions_assigns_types_accurately()
var (file, cursors) = ParserHelper.GetFileWithCursors(@"
var fo|o = reduce([123], 0, (c|ur, next) => cur + next)
var fo|o2 = reduce(['abc', 'def'], '', (cur, nex|t) => concat(cur, next))
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand Down Expand Up @@ -256,7 +263,8 @@ public void Lambda_functions_can_be_nested()
)
)
)
");
",
'|');

var result = CompilationHelper.Compile(file);
result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics();
Expand Down
1 change: 0 additions & 1 deletion src/Bicep.Core.IntegrationTests/ModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ public void Module_cannot_use_resource_typed_output_without_feature_enabled()
"));
result.Should().HaveDiagnostics(new[]
{
("BCP231", DiagnosticLevel.Error, "Using resource-typed parameters and outputs requires enabling EXPERIMENTAL feature BICEP_RESOURCE_TYPED_PARAMS_AND_OUTPUTS_EXPERIMENTAL."),
("BCP104", DiagnosticLevel.Error, "The referenced module has errors.")
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Core.IntegrationTests/OutputsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ import storage as stg {
");
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new []
{
("BCP228", DiagnosticLevel.Error, "The type \"container\" cannot be used as an output type. Extensibility types are currently not supported as parameters or outputs."),
("BCP227", DiagnosticLevel.Error, "The type \"container\" cannot be used as a parameter or output type. Extensibility types are currently not supported as parameters or outputs."),
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Core.IntegrationTests/ParametersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ import storage as stg {
");
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new []
{
("BCP227", DiagnosticLevel.Error, "The type \"container\" cannot be used as a parameter type. Extensibility types are currently not supported as parameters or outputs."),
("BCP227", DiagnosticLevel.Error, "The type \"container\" cannot be used as a parameter or output type. Extensibility types are currently not supported as parameters or outputs."),
("BCP062", DiagnosticLevel.Error, "The referenced declaration with name \"container\" is not valid."),
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ param l
("BCP028", DiagnosticLevel.Error, "Identifier \"l\" is declared multiple times. Remove or rename the duplicates."),
("BCP079", DiagnosticLevel.Error, "This expression is referencing its own declaration, which is not allowed."),
("BCP028", DiagnosticLevel.Error, "Identifier \"l\" is declared multiple times. Remove or rename the duplicates."),
("BCP014", DiagnosticLevel.Error, "Expected a parameter type at this location. Please specify one of the following types: \"array\", \"bool\", \"int\", \"object\", \"string\"."),
("BCP279", DiagnosticLevel.Error, "Expected a type at this location. Please specify a valid type expression or one of the following types: \"array\", \"bool\", \"int\", \"object\", \"string\"."),
});
}

Expand Down Expand Up @@ -1818,7 +1818,7 @@ public void Test_Issue2484()

result.ExcludingLinterDiagnostics().Should().HaveDiagnostics(new[]
{
("BCP027", DiagnosticLevel.Error, "The parameter expects a default value of type \"'apple' | 'banana'\" but provided value is of type \"'peach'\"."),
("BCP033", DiagnosticLevel.Error, "Expected a value of type \"'apple' | 'banana'\" but the provided value is of type \"'peach'\"."),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public async Task NameBindingsShouldBeConsistent(DataSet dataSet)
symbol.Should().Match(s =>
s is MetadataSymbol ||
s is ParameterSymbol ||
s is TypeAliasSymbol ||
s is VariableSymbol ||
s is ResourceSymbol ||
s is ModuleSymbol ||
Expand All @@ -135,6 +136,7 @@ s is BuiltInNamespaceSymbol ||
s is ErrorSymbol ||
s is MetadataSymbol ||
s is ParameterSymbol ||
s is TypeAliasSymbol ||
s is VariableSymbol ||
s is ResourceSymbol ||
s is ModuleSymbol ||
Expand Down
5 changes: 4 additions & 1 deletion src/Bicep.Core.Samples/DataSets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public static class DataSets

public static DataSet InvalidTargetScopes_LF => CreateDataSet();

public static DataSet InvalidTypeDeclarations_LF => CreateDataSet();

public static DataSet InvalidVariables_LF => CreateDataSet();

public static DataSet LargeTemplate_Stress_LF => CreateDataSet();
Expand Down Expand Up @@ -63,6 +65,8 @@ public static class DataSets

public static DataSet ResourcesTenant_CRLF => CreateDataSet();

public static DataSet TypeDeclarations_LF => CreateDataSet();

public static DataSet Unicode_LF => CreateDataSet();

public static DataSet Variables_LF => CreateDataSet();
Expand Down Expand Up @@ -119,4 +123,3 @@ public static class DataSets
private static DataSet CreateDataSet([CallerMemberName] string? dataSetName = null) => new DataSet(dataSetName!);
}
}

Loading

0 comments on commit 3f448ab

Please sign in to comment.